File Coverage

blib/lib/Tapper/PRC/Testcontrol.pm
Criterion Covered Total %
statement 283 424 66.7
branch 89 204 43.6
condition 20 46 43.4
subroutine 28 32 87.5
pod 16 17 94.1
total 436 723 60.3


line stmt bran cond sub pod time code
1             package Tapper::PRC::Testcontrol;
2             our $AUTHORITY = 'cpan:TAPPER';
3             $Tapper::PRC::Testcontrol::VERSION = '5.0.4';
4 24     24   3475014 use 5.010;
  24         246  
5 24     24   108 use warnings;
  24         44  
  24         615  
6 24     24   104 use strict;
  24         38  
  24         563  
7              
8 24     24   12278 use IPC::Open3;
  24         61594  
  24         1125  
9 24     24   10340 use File::Copy;
  24         52511  
  24         1616  
10 24     24   2629 use File::Temp qw/tempdir/;
  24         54036  
  24         1060  
11 24     24   11949 use Moose;
  24         9657012  
  24         161  
12 24     24   164117 use YAML 'LoadFile';
  24         151093  
  24         1213  
13 24     24   170 use File::Basename 'dirname';
  24         37  
  24         1584  
14 24     24   14558 use English '-no_match_vars';
  24         36118  
  24         134  
15 24     24   8882 use IO::Handle;
  24         245  
  24         990  
16 24     24   126 use File::Basename qw/basename dirname/;
  24         44  
  24         869  
17              
18 24     24   10522 use Tapper::Remote::Config;
  24         960285  
  24         17240  
19             # ABSTRACT: Control running test programs
20              
21             extends 'Tapper::PRC';
22              
23             our $MAXREAD = 1024; # read that much in one read
24              
25              
26              
27             sub capture_handler_tap
28             {
29 2     2 1 25 my ($self, $filename) = @_;
30 2         25 my $content;
31 2 50       153 open my $fh, '<', $filename or die "Can not open $filename to send captured report";
32 2         13 { local $/; $content = <$fh> }
  2         37  
  2         83  
33 2         44 close $fh;
34 2         33 return $content;
35             }
36              
37              
38             sub send_output
39             {
40 2     2 1 12 my ($self, $captured_output, $testprogram) = @_;
41              
42             # add missing minimum Tapper meta information
43 2         20 my $headerlines = "";
44 2 50       306 $headerlines .= "# Tapper-suite-name: ".basename($testprogram->{program})."\n" unless $captured_output =~ /\# Tapper-suite-name:/;
45 2 50       282 $headerlines .= "# Tapper-machine-name: ".$self->cfg->{hostname}."\n" unless $captured_output =~ /\# Tapper-machine-name:/;
46 2 50       68 $headerlines .= "# Tapper-reportgroup-testrun: ".$self->cfg->{test_run}."\n" unless $captured_output =~ /\# Tapper-reportgroup-testrun:/;
47              
48 2         32 $captured_output =~ s/^(1\.\.\d+\n)/$1$headerlines/m;
49              
50 2         114 my ($error, $message) = $self->tap_report_away($captured_output);
51              
52 2 50       3565 return $message if $error;
53 2         10 return 0;
54              
55             }
56              
57              
58             sub send_attachements {
59              
60 2     2 0 22 my ( $self ) = @_;
61              
62             my ( $b_error, $s_message ) = $self->tap_report_away(
63             "TAP version 13\n"
64             . "1..1\n"
65             . "# Tapper-suite-name: PRC" . ( $self->cfg->{guest_number} || 0 ) . "-Attachments\n"
66             . "# Tapper-machine-name: " . $self->cfg->{hostname} . "\n"
67 2   50     127 . "# Tapper-reportgroup-testrun: " . $self->cfg->{test_run} . "\n"
68             . "ok - Test attachments\n"
69             );
70              
71 2 50       1574 return ( 1, $s_message ) if $b_error;
72              
73 0         0 $self->upload_files( $s_message );
74              
75 0         0 return ( 0, q## );
76              
77             }
78              
79              
80             sub upload_files
81             {
82              
83 1     1 1 2006746 my ( $or_self, $i_reportid ) = @_;
84              
85 1         243 my $s_host = $or_self->cfg->{report_server};
86 1         54 my $i_port = $or_self->cfg->{report_api_port};
87 1         9 my $s_path = $ENV{TAPPER_OUTPUT_PATH};
88              
89 1 50       64 return 0 unless -d $s_path;
90              
91 1         10111 my @a_files = `find $s_path -type f`;
92              
93 1         134 $or_self->log->debug( @a_files );
94              
95 1         1174 foreach my $s_file( @a_files ) {
96              
97 1         9 chomp $s_file;
98              
99 1         6 my $s_reportfile = $s_file;
100 1         49 $s_reportfile =~ s|^$s_path/*||;
101             #$s_reportfile =~ s|^./||;
102             #$s_reportfile =~ s|[^A-Za-z0-9_-]|_|g;
103              
104 1         54 my $or_server = IO::Socket::INET->new(
105             PeerAddr => $s_host,
106             PeerPort => $i_port,
107             );
108              
109 1 50       1273 return "Cannot open remote receiver $s_host:$i_port" if not $or_server;
110              
111 1 50       47 open( my $fh_file, "<", $s_file ) or do{$or_self->log->warn("Can't open $s_file:$!"); $or_server->close();next;};
  0         0  
  0         0  
  0         0  
112 1         51 $or_server->print("#! upload $i_reportid $s_reportfile plain\n");
113 1         74 while ( my $line = <$fh_file> ) {
114 1         12 $or_server->print($line);
115             }
116 1         45 close($fh_file);
117 1         27 $or_server->close();
118 1         148 unlink $s_file; # so we don't upload file again when MCP uploads remaining bits
119             }
120              
121 1         21 return 0;
122              
123             }
124              
125              
126             sub get_appendix {
127 30     30 1 13978 my($self, $output) = @_;
128 30         214 my $appendix = '';
129 30 100 66     1131 if (-e "$output.stdout" or -e "$output.stderr") {
130 12         1139 my $basename = basename($output);
131 12         379 my $dirname = dirname ($output);
132 12         1101 my @files = <$dirname/$basename-*.stdout>;
133              
134 24     24   223 no warnings 'uninitialized';
  24         53  
  24         88249  
135 12         89 my @appendizes = sort map { my ($append) = m/(\d+)\D*$/; $append} @files;
  3         21  
  3         12  
136 12         252 $appendix = sprintf("-%03d",shift(@appendizes) + 1);
137             }
138 30         207 return $appendix;
139             }
140              
141              
142             sub kill_process
143             {
144 6     6 1 36 my ($pid) = @_;
145              
146             # allow testprogram to react on SIGTERM, then do SIGKILL
147 6         432 kill ('SIGTERM', $pid);
148 6         108 waitpid $pid, 0;
149 6 50       28 my $grace_period = $ENV{HARNESS_ACTIVE} ? 0 : 2;
150 6   33     22 while ( $grace_period > 0 and (kill 0, $pid) ) {
151 0         0 $grace_period--;
152 0         0 sleep 1;
153             }
154 6 100       42 if (kill 0, $pid) {
155 2         14 kill 'SIGKILL', $pid;
156 2         16 waitpid $pid, 0;
157             }
158             }
159              
160              
161             sub get_process_tree
162             {
163 4     4 1 16 my ($pid) = @_;
164              
165 4 50 33     68 return () unless $pid && $pid > 1;
166              
167 4         2080 require Proc::Killfam;
168 4         10836 require Proc::ProcessTable;
169 4         22 return Proc::Killfam::get_pids(Proc::ProcessTable->new->table, $pid);
170             }
171              
172              
173             sub kill_process_tree
174             {
175 4     4 1 12 my ($pid) = @_;
176              
177 4 50       24 return unless $pid > 1;
178              
179 4         36 my @pids = get_process_tree($pid);
180 4         26280 kill_process($_) foreach ($pid, @pids);
181 4 100       60 if (@pids) { kill_process_tree($_) foreach @pids }
  2         28  
182             }
183              
184              
185             sub testprogram_execute
186             {
187 21     21 1 62260618 my ($self, $test_program) = @_;
188              
189 21         221 my $program = $test_program->{program};
190 21         134 my $chdir = $test_program->{chdir};
191 21         1665 my $progpath = $self->cfg->{paths}{testprog_path};
192 21         102 my $output = $program;
193 21         333 $output =~ s|[^A-Za-z0-9_-]|_|g;
194 21         158 $output = $test_program->{out_dir}.$output;
195              
196              
197 21 100       272 if ($program !~ m(^/)) {
198 18         401 $ENV{PATH} = "$progpath:$ENV{PATH}";
199 18         144932 $program = qx(which $program);
200 18         515 chomp $program;
201             }
202              
203             # try to catch non executables early
204 21 100       985 if (-e $program) {
205 19 50       426 if (not -x $program) {
206 0         0 system ("chmod", "ugo+x", $program);
207 0 0       0 return("tried to execute $program which is not an execuable and can not set exec flag") if not -x $program;
208             }
209              
210 19 50       479 return("tried to execute $program which is a directory") if -d $program;
211 19 50 33     616 return("tried to execute $program which is a special file (FIFO, socket, device, ..)") unless -f $program or -l $program;
212             }
213              
214 21 50       126 foreach my $file (@{$test_program->{upload_before} || [] }) {
  21         1101  
215              
216 0         0 my $target_name =~ s|[^A-Za-z0-9_-]|_|g;
217 0         0 $target_name = $test_program->{out_dir}.'/before/'.$target_name;
218 0         0 File::Copy::copy($file, $target_name);
219              
220             }
221              
222 21         859 $self->log->info("Try to execute test suite $program");
223              
224 21         8750 my $appendix = $self->get_appendix($output);
225 21         2159 pipe (my $read, my $write);
226 21 50 33     485 return ("Can't open pipe:$!") if not (defined $read and defined $write);
227              
228 21         37949 my $pid=fork();
229 21 50       1484 return( "fork failed: $!" ) if not defined($pid);
230              
231 21 100       1212 if ($pid == 0) { # hello child
232 9         439 close $read;
233              
234 9 50       821 %ENV = (%ENV, %{$test_program->{environment} || {} });
  9         4141  
235 9 50       2001 open (STDOUT, ">", "$output$appendix.stdout") or syswrite($write, "Can't open output file $output$appendix.stdout: $!"),exit 1;
236 9 50       627 open (STDERR, ">", "$output$appendix.stderr") or syswrite($write, "Can't open output file $output$appendix.stderr: $!"),exit 1;
237 9 50       103 if ($chdir) {
238 0 0 0     0 if (-d $chdir) {
    0          
239 0         0 chdir $chdir;
240             } elsif ($chdir eq "AUTO" and $program =~ m,^/, ) {
241 0         0 chdir dirname($program);
242             }
243             }
244 9 100       71 exec ($program, @{$test_program->{argv} || []}) or syswrite($write,"$!\n");
  9 0       0  
245 0         0 close $write;
246 0         0 exit -1;
247             } else {
248              
249             # hello parent
250 12         421 close $write;
251              
252 12         130 my $killed;
253             my $sig_name;
254             my $signal_kill = sub {
255 2     2   32 $killed = 1;
256 2         44 ($sig_name) = @_;
257 2         78 $self->log->warn("Catched signal $sig_name");
258 2         192 kill_process_tree ($pid);
259 12         714 };
260 12         639 local $SIG{ALRM} = $signal_kill;
261 12         342 local $SIG{TERM} = $signal_kill;
262 12         258 local $SIG{KILL} = $signal_kill;
263 12         335 local $SIG{QUIT} = $signal_kill;
264 12         353 local $SIG{INT} = $signal_kill;
265              
266 12   100     366 alarm ($test_program->{timeout} || 0);
267 12         12330379 waitpid($pid,0);
268 12         242 my $retval = $?;
269 12         122 alarm(0);
270              
271 12 50       75 foreach my $file (@{$test_program->{upload_after} || [] }) {
  12         488  
272 0         0 my $target_name =~ s|[^A-Za-z0-9_-]|_|g;
273 0         0 $target_name = $test_program->{out_dir}.'/after/'.$target_name;
274 0         0 File::Copy::copy($file, $target_name);
275             }
276 12 100       213 if ($test_program->{capture}) {
277 2         6 my $captured_output;
278 2 50       26 if ( $test_program->{capture} eq 'tap' ) {
    0          
279 2         24 eval { $captured_output = $self->capture_handler_tap("$output$appendix.stdout")};
  2         69  
280 2 50       317 return $@ if $@;
281             }
282             elsif ( $test_program->{capture} eq 'tap-stderr' ) {
283 0         0 eval { $captured_output = $self->capture_handler_tap("$output$appendix.stderr")};
  0         0  
284 0 0       0 return $@ if $@;
285             }
286             else {
287 0         0 return "Can not handle captured output, unknown capture type '$test_program->{capture}'. Valid types are (tap)";
288             }
289 2         44 my ( $b_error, $error_msg ) = $self->send_output($captured_output, $test_program);
290 2 50       29 return $error_msg if $b_error;
291             }
292              
293 12 100       89 if ($killed) {
294             return
295             "Killed $program after SIG:$sig_name"
296             .(
297             $sig_name eq "ALRM"
298 2 50       204 ? " (timeout ".$test_program->{timeout}." seconds)"
299             : ""
300             );
301             }
302              
303 10 100       458 if ( $retval ) {
304 3         26 my $error;
305 3         66 sysread($read,$error, $MAXREAD);
306 3         38 $error =~ s/[\r\n]//g;
307 3         294 return("Executing $program failed:$error");
308             }
309             }
310 7         1605 return 0;
311             }
312              
313              
314             sub guest_start
315             {
316 1     1 1 3598 my ($self) = @_;
317 1         2 my ($error, $retval);
318             GUEST:
319 1         2 for (my $i=0; $i<=$#{$self->cfg->{guests}}; $i++) {
  3         236  
320 2         41 my $guest = $self->cfg->{guests}->[$i];
321 2 50       16 if ($guest->{exec}){
    100          
    50          
322 0         0 my $startscript = $guest->{exec};
323 0         0 $self->log->info("Try to start virtualisation guest with $startscript");
324 0 0       0 if (not -s $startscript) {
325 0         0 $self->mcp_send({prc_number => ($i+1), state => 'error-guest',
326             error => qq(Startscript "$startscript" is empty or does not exist at all)});
327 0         0 next GUEST;
328             } else {
329             # just try to set it executable always
330 0 0       0 if (not -x $startscript) {
331 0 0       0 unless (system ("chmod", "ugo+x", $startscript) == 0) {
332 0         0 $self->mcp_send({prc_number => ($i+1), state => 'error-guest',
333             error =>
334             return qq(Unable to set executable bit on "$startscript": $!)
335             });
336 0         0 next GUEST;
337             }
338             }
339             }
340 0 0       0 if (not system($startscript) == 0 ) {
341 0         0 $retval = qq(Can't start virtualisation guest using startscript "$startscript");
342 0         0 $self->mcp_send({prc_number => ($i+1), state => 'error-guest',
343             error => $retval});
344 0         0 next GUEST;
345             }
346             } elsif ($guest->{svm}){
347 1         5457 my $xm = `which xm`; chomp $xm;
  1         34  
348 1         89 $self->log->info("Try load Xen guest described in ",$guest->{svm});
349 1         226 ($error, $retval) = $self->log_and_exec($xm, 'create', $guest->{svm});
350 1 50       4418 if ($error) {
351 1         52 $self->mcp_send({prc_number => ($i+1), state => 'error-guest',
352             error => $retval});
353 1         25 next GUEST;
354             }
355             } elsif ($guest->{xen}) {
356 1         6 $self->log->info("Try load Xen guest described in ",$guest->{xen});
357              
358 1         378 my $guest_file = $guest->{xen};
359 1 50       7 if ($guest_file =~ m/^(.+)\.(?:xl|svm)$/) {
360 1         4 $guest_file = $1;
361             }
362              
363 1         4004 my $xm = `which xm`; chomp $xm;
  1         26  
364 1         2792 my $xl = `which xl`; chomp $xl;
  1         59  
365              
366 1 50       48 if ( -e $xl ) {
    0          
367 1         77 ($error, $retval) = $self->log_and_exec($xl, 'create', $guest_file.".xl");
368 1 50       4533 if ($error) {
369 1         62 $self->mcp_send({prc_number => ($i+1), state => 'error-guest',
370             error => $retval});
371 1         25 next GUEST;
372             }
373             } elsif ( -e $xm ) {
374 0         0 ($error, $retval) = $self->log_and_exec($xm, 'create', $guest_file.".svm");
375 0 0       0 if ($error) {
376 0         0 $self->mcp_send({prc_number => ($i+1), state => 'error-guest',
377             error => $retval});
378 0         0 next GUEST;
379             }
380             } else {
381 0         0 $retval = "Can not find both xm and xl.";
382 0         0 $self->mcp_send({prc_number => ($i+1), state => 'error-guest',
383             error => $retval});
384 0         0 next GUEST;
385             }
386             }
387 0         0 $self->mcp_send({prc_number => ($i+1), state => 'start-guest'});
388             }
389 1         14 return 0;
390             }
391              
392              
393             sub create_log
394             {
395 8     8 1 54 my ($self) = @_;
396 8         568 my $testrun = $self->cfg->{test_run};
397             my $output_dir = ($self->cfg->{test_type} || '') eq 'minion'
398             ? $self->cfg->{paths}{minion_output_dir}
399 8 50 50     205 : $self->cfg->{paths}{output_dir};
400 8         64 my $outdir = "$output_dir/$testrun/test/";
401 8         89 my ($error, $retval);
402              
403 8         66 for (my $i = 0; $i <= $#{$self->cfg->{guests}}; $i++) {
  8         280  
404             # guest count starts with 1, arrays start with 0
405 0         0 my $guest_number=$i+1;
406              
407             # every guest gets its own subdirectory
408 0         0 my $guestoutdir="$outdir/guest-$guest_number/";
409              
410 0         0 $error = $self->makedir($guestoutdir);
411 0 0       0 return $error if $error;
412              
413 0         0 $self->log_and_exec("touch $guestoutdir/console");
414 0         0 $self->log_and_exec("chmod 666 $guestoutdir/console");
415 0         0 ($error, $retval) = $self->log_and_exec("ln -sf $guestoutdir/console /tmp/guest$guest_number.fifo");
416 0 0       0 return "Can't create guest console file $guestoutdir/console: $retval" if $error;
417             }
418 8         154 return 0;
419             }
420              
421              
422             sub nfs_mount
423             {
424 0     0 1 0 my ($self) = @_;
425 0         0 my ($error, $retval);
426              
427 0         0 $error = $self->makedir($self->cfg->{paths}{prc_nfs_mountdir});
428 0 0       0 return $error if $error;
429              
430 0         0 ($error, $retval) = $self->log_and_exec("mount",$self->cfg->{paths}{prc_nfs_mountdir});
431 0 0       0 return 0 if not $error;
432 0         0 ($error, $retval) = $self->log_and_exec("mount",$self->cfg->{prc_nfs_server}.":".$self->cfg->{paths}{prc_nfs_mountdir},$self->cfg->{paths}{prc_nfs_mountdir});
433             # report error, but only if not already mounted
434 0 0 0     0 return "Can't mount ".$self->cfg->{paths}{prc_nfs_mountdir}.":$retval" if ($error and ! -d $self->cfg->{paths}{prc_nfs_mountdir}."/live");
435 0         0 return 0;
436             }
437              
438              
439             sub control_testprogram
440             {
441 11     11 1 2700 my ($self) = @_;
442              
443 11         297 $ENV{TAPPER_TESTRUN} = $self->cfg->{test_run};
444 11         346 $ENV{TAPPER_SERVER} = $self->cfg->{mcp_server};
445 11         260 $ENV{TAPPER_REPORT_SERVER} = $self->cfg->{report_server};
446 11         233 $ENV{TAPPER_REPORT_API_PORT} = $self->cfg->{report_api_port};
447 11         210 $ENV{TAPPER_REPORT_PORT} = $self->cfg->{report_port};
448 11         258 $ENV{TAPPER_HOSTNAME} = $self->cfg->{hostname};
449 11 50       234 $ENV{TAPPER_REBOOT_COUNTER} = $self->cfg->{reboot_counter} if $self->cfg->{reboot_counter};
450 11 50       224 $ENV{TAPPER_MAX_REBOOT} = $self->cfg->{max_reboot} if $self->cfg->{max_reboot};
451 11   50     232 $ENV{TAPPER_GUEST_NUMBER} = $self->cfg->{guest_number} || 0;
452 11 100       253 $ENV{TAPPER_SYNC_FILE} = $self->cfg->{syncfile} if $self->cfg->{syncfile};
453 11         345 $ENV{TAPPER_SYNC_PATH} = $self->cfg->{paths}{sync_path}; # if -d ($self->cfg->{paths}{sync_path} || '');
454 11 50       47 if ($self->{cfg}->{testplan}) {
455 0         0 $ENV{TAPPER_TESTPLAN_ID} = $self->cfg->{testplan}{id};
456 0         0 $ENV{TAPPER_TESTPLAN_PATH} = $self->cfg->{testplan}{path};
457             }
458              
459 11         220 my $test_run = $self->cfg->{test_run};
460             my $output_dir = ($self->cfg->{test_type} || '') eq 'minion'
461             ? $self->cfg->{paths}{minion_output_dir}
462 11 50 50     231 : $self->cfg->{paths}{output_dir};
463 11         42 my $out_dir = "$output_dir/$test_run/test/";
464 11         26 my @testprogram_list;
465 11 50       278 @testprogram_list = @{$self->cfg->{testprogram_list}} if $self->cfg->{testprogram_list};
  11         223  
466              
467             # prepend outdir with guest number if we are in virtualisation guest
468 11 50       61 $out_dir.="guest-".$self->{cfg}->{guest_number}."/" if $self->{cfg}->{guest_number};
469              
470 11         216 my $error = $self->makedir($out_dir);
471              
472             # can't create output directory. Make
473 11 50       117113 if ($error) {
474 0         0 $self->log->warn($error);
475 0         0 $out_dir = tempdir( CLEANUP => 1 );
476             }
477              
478 11         350 $ENV{TAPPER_OUTPUT_PATH} = $out_dir;
479              
480 11 50       1279 if ($self->cfg->{test_program}) {
481 0         0 my $argv;
482             my $environment;
483 0         0 my $chdir;
484 0 0       0 $argv = $self->cfg->{parameters} if $self->cfg->{parameters};
485 0 0       0 $environment = $self->cfg->{environment} if $self->cfg->{environment};
486 0 0       0 $chdir = $self->cfg->{chdir} if $self->cfg->{chdir};
487 0   0     0 my $timeout = $self->cfg->{timeout_testprogram} || 0;
488 0         0 $timeout = int $timeout;
489 0         0 my $runtime = $self->cfg->{runtime};
490             push (@testprogram_list, {program => $self->cfg->{test_program},
491             chdir => $chdir,
492             parameters => $argv,
493             environment => $environment,
494             timeout => $timeout,
495             runtime => $runtime,
496             upload_before => $self->cfg->{upload_before},
497             upload_after => $self->cfg->{upload_after},
498 0         0 });
499             }
500              
501              
502 11         173 for (my $i=0; $i<=$#testprogram_list; $i++) {
503 15         199 my $testprogram = $testprogram_list[$i];
504              
505 15   100     343 $ENV{TAPPER_TS_RUNTIME} = $testprogram->{runtime} || 0;
506              
507             # unify differences in program vs. program_list vs. virt
508 15   33     173 $testprogram->{program} ||= $testprogram->{test_program};
509 15   66     285 $testprogram->{timeout} ||= $testprogram->{timeout_testprogram};
510 15 100       76 $testprogram->{argv} = $testprogram->{parameters} if @{$testprogram->{parameters} || []};
  15 100       299  
511              
512             # create hash for testprogram_execute
513 15   50     107 $testprogram->{timeout} ||= 0;
514 15         121 $testprogram->{out_dir} = $out_dir;
515              
516 15         200 my $retval = $self->testprogram_execute($testprogram);
517              
518 10 100       106 if ($retval) {
519 3         19 my $error_msg = "Error while executing $testprogram->{program}: $retval";
520 3         135 $self->mcp_inform({testprogram => $i, state => 'error-testprogram', error => $error_msg});
521 3         3994 $self->log->info($error_msg);
522             } else {
523 7         261 $self->mcp_inform({testprogram => $i , state => 'end-testprogram'});
524 7         3349 $self->log->info("Successfully finished test suite $testprogram->{program}");
525             }
526              
527             }
528              
529 6         2472 return(0);
530             }
531              
532              
533             sub get_peers_from_file
534             {
535 0     0 1 0 my ($self, $file) = @_;
536 0         0 my $peers;
537              
538 0         0 $peers = LoadFile($file);
539 0 0       0 return "Syncfile does not contain a list of host names" if not ref($peers) eq 'ARRAY';
540              
541 0         0 my $hostname = $self->cfg->{hostname};
542 0         0 my %peerhosts;
543 0         0 foreach my $host (@$peers) {
544 0         0 $peerhosts{$host} = 1;
545             }
546 0         0 delete $peerhosts{$hostname};
547              
548 0         0 return \%peerhosts;
549             }
550              
551              
552             sub wait_for_sync
553             {
554 0     0 1 0 my ($self, $syncfile) = @_;
555              
556 0         0 my %peerhosts; # easier to delete than from array
557              
558 0         0 eval {
559 0         0 %peerhosts = %{$self->get_peers_from_file($syncfile)};
  0         0  
560             };
561 0 0       0 return $@ if $@;
562              
563              
564 0         0 my $hostname = $self->cfg->{hostname};
565 0         0 my $port = $self->cfg->{sync_port};
566 0         0 my $sync_srv = IO::Socket::INET->new( LocalPort => $port, Listen => 5, );
567 0         0 my $select = IO::Select->new($sync_srv);
568              
569 0         0 $self->log->info("Trying to sync with: ". join(", ",sort keys %peerhosts));
570              
571 0         0 foreach my $host (keys %peerhosts) {
572 0         0 my $remote = IO::Socket::INET->new(PeerPort => $port, PeerAddr => $host,);
573 0 0       0 if ($remote) {
574 0         0 $remote->print($hostname);
575 0         0 $remote->close();
576 0         0 delete($peerhosts{$host});
577             }
578 0 0       0 if ($select->can_read(0)) {
579 0         0 my $msg_srv = $sync_srv->accept();
580 0         0 my $remotehost;
581 0         0 $msg_srv->read($remotehost, 2048); # no hostnames are that long, anything longer is wrong and can be ignored
582 0         0 chomp $remotehost;
583 0         0 $msg_srv->close();
584 0 0       0 if ($peerhosts{$remotehost}) {
585 0         0 delete($peerhosts{$remotehost});
586             } else {
587 0         0 $self->log->warn(qq(Received sync request from host "$remotehost" which is not in our peerhost list. Request was sent from ),$msg_srv->peerhost);
588             }
589             }
590 0         0 $self->log->debug("In sync with $host.");
591              
592             }
593              
594 0         0 while (%peerhosts) {
595 0 0       0 if ($select->can_read()) { # TODO: timeout handling
596 0         0 my $msg_srv = $sync_srv->accept();
597 0         0 my $remotehost;
598 0         0 $msg_srv->read($remotehost, 2048); # no hostnames are that long, anything longer is wrong and can be ignored
599 0         0 chomp $remotehost;
600 0         0 $msg_srv->close();
601 0 0       0 if ($peerhosts{$remotehost}) {
602 0         0 delete($peerhosts{$remotehost});
603 0         0 $self->log->debug("In sync with $remotehost.");
604             } else {
605 0         0 $self->log->warn(qq(Received sync request from host "$remotehost" which is not in our peerhost list. Request was sent from ),$msg_srv->peerhost);
606             }
607             } else {
608             # handle timeout here when can_read() has a timeout eventually
609             }
610             }
611 0         0 return 0;
612             }
613              
614              
615             sub send_keep_alive_loop
616             {
617 0     0 1 0 my ($self, $sleeptime) = @_;
618 0 0       0 return unless $sleeptime;
619 0         0 while (1) {
620 0         0 $self->mcp_inform("keep-alive");
621 0         0 sleep($sleeptime);
622             }
623 0         0 return;
624             }
625              
626              
627             sub run
628             {
629 8     8 1 6014050 my ($self) = @_;
630              
631 8         691 my $producer = Tapper::Remote::Config->new();
632 8         13978 my $config = $producer->get_local_data("test-prc0");
633              
634 8         7814 $self->cfg($config);
635              
636 8         262 $0 = "tapper-prc-testcontrol-".$self->cfg->{test_run};
637              
638 8 100       265 $self->cfg->{reboot_counter} = 0 if not defined($self->cfg->{reboot_counter});
639              
640 8 50       267 if ($self->cfg->{log_to_file}) {
641 0         0 $self->log_to_file('testing');
642             }
643              
644             # ignore error
645 8         318 $self->log_and_exec('ntpdate -s gwo');
646              
647 8 50       14326 if ($config->{prc_nfs_server}) {
648 0 0       0 if ( my $retval = $self->nfs_mount() ) {
649 0         0 $self->log->warn($retval);
650             }
651             }
652              
653 8 50       248 if ( my $retval = $self->create_log() ) {
654 0         0 $self->log->logdie($retval);
655             }
656              
657 8 50       119 if ($config->{scenario_id}) {
658 0         0 my $syncfile = $config->{paths}{sync_path}."/".$config->{scenario_id}."/syncfile";
659 0 0       0 if (-e $syncfile) {
660 0         0 $self->cfg->{syncfile} = $syncfile;
661              
662 0 0       0 if ( my $retval = $self->wait_for_sync($syncfile) ) {
663 0         0 $self->log->logdie("Can not sync - $retval");
664             }
665             }
666             }
667              
668 8 50       64 if ($self->{cfg}->{guest_count}) {
669 0 0       0 if ( my $retval = $self->guest_start() ) {
670 0         0 $self->log->error($retval);
671             }
672             }
673              
674 8 100       324 if ( not $self->cfg->{reboot_counter} ) {
675 7         580 $self->mcp_inform({state => 'start-testing'});
676             }
677              
678 8 100 66     17205 if ( $self->cfg->{test_program} or $self->cfg->{testprogram_list} ) {
679 6         111 $self->control_testprogram();
680             }
681              
682 4 100       150 if ($self->cfg->{max_reboot}) {
683 2         1045 $self->mcp_inform({state => 'reboot', count => $self->cfg->{reboot_counter}, max_reboot => $self->cfg->{max_reboot}});
684 2 50       12843 if ($self->cfg->{reboot_counter} < $self->cfg->{max_reboot}) {
685 2         40 $self->cfg->{reboot_counter}++;
686 2 50       14 YAML::Syck::DumpFile($config->{filename}, $self->{cfg}) or $self->mcp_error("Can't write config to file: $!");
687 2         1171 $self->log_and_exec("reboot");
688 2         39 return 0;
689             }
690              
691             }
692              
693             # TODO: too cheap work-around
694             #
695             # Make sure last end-testing can't overtake last
696             # end-testprogram (Yes, this did happen).
697             #
698             # Wait at least as long as the MCP loop time is, see
699             # tapper.cfg:
700             # - mcp.child.get_message_sleep_interval
701             # - times.poll_intervall
702              
703              
704 2 50       2000274 sleep ($ENV{HARNESS_ACTIVE} ? 1 : 10);
705              
706             # send attachment report
707 2         116 my ( $b_error, $s_error_msg ) = $self->send_attachements();
708 2 50       18 if ( $b_error ) {
709 2         21 $self->log->error( $s_error_msg );
710             }
711              
712 2         101 $self->mcp_inform({state => 'end-testing'});
713              
714 2         1582 return 1;
715              
716             }
717              
718             1;
719              
720             __END__
721              
722             =pod
723              
724             =encoding UTF-8
725              
726             =head1 NAME
727              
728             Tapper::PRC::Testcontrol - Control running test programs
729              
730             =head1 FUNCTIONS
731              
732             =head2 capture_handler_tap
733              
734             This function is a handler for the capture function. It handles capture
735             requests of type 'tap'. This means the captured output is supposed to be
736             TAP already and therefore no transformation is needed.
737              
738             @param file handle - opened file handle
739              
740             @return string - output in TAP format
741             @return error - die()
742              
743             =head2 send_output
744              
745             Send the captured TAP output to the report receiver.
746              
747             @param string - TAP text
748              
749             @return success - 0
750             @return error - error string
751              
752             =head2 send_output
753              
754             Send the a attachment to the report receiver and add attachements.
755              
756             @return success - 0
757             @return error - error string
758              
759             =head2 upload_files
760              
761             Upload files written in one stage of the testrun to report framework.
762              
763             @param int - report id
764             @param int - testrun id
765              
766             @return success - 0
767             @return error - error string
768              
769             =head2 get_appendix
770              
771             For testprogram with the same name the output file names will be
772             identical. To prevent this, we append a serial number. This function
773             calculates this appendix and returns the next one to use. If no such
774             serial is needed because no output file of the given name exists yet the
775             empty string is returned.
776              
777             @param string - name of the output file without appendix
778              
779             @return string - string to append to output file name to make it unique
780              
781             =head2 kill_process($pid)
782              
783             Gracefully kill a single process.
784              
785             =head2 get_process_tree($pid)
786              
787             Get list of children for a process. The process itself is not
788             contained in the list.
789              
790             =head2 kill_process_tree($pid)
791              
792             Kill whole tree of processes, depth-first, with extreme prejudice.
793              
794             =head2 testprogram_execute
795              
796             Execute one testprogram. Handle all error conditions.
797              
798             @param hash ref - contains all config options for program to execute
799             * program - program name
800             * timeout - timeout in seconds
801             * outdir - output directory
802             * parameters - arrayref of strings - parameters for test program
803             * environment - hashref of strings - environment variables for test program
804             * chdir - string - where to chdir before executing the testprogram
805              
806             @return success - 0
807             @return error - error string
808              
809             =head2 guest_start
810              
811             Start guest images for virtualisation. Only Xen guests can be started at the
812             moment.
813              
814             @return success - 0
815             @return error - error string
816              
817             =head2 create_log
818              
819             Checks whether fifos for guest logging exists and creates them if
820             not. Existing files of wrong type are deleted.
821              
822             @retval success - 0
823             @retval error - error string
824              
825             =head2 nfs_mount
826              
827             Mount the output directory from an NFS server. This method is used since we
828             only want to mount this NFS share in live mode.
829              
830             @return success - 0
831             @return error - error string
832              
833             =head2 control_testprogram
834              
835             Control running of one program including caring for its input, output and
836             the environment variables some testers asked for.
837              
838             @return success - 0
839             @return error - error string
840              
841             =head2 get_peers_from_file
842              
843             Read syncfile and extract list of peer hosts (not including this host).
844              
845             @param string - file name
846              
847             @return success - hash ref
848              
849             @throws plain error message
850              
851             =head2 wait_for_sync
852              
853             Synchronise with other hosts belonging to the same interdependent testrun.
854              
855             @param array ref - list of hostnames of peer machines
856              
857             @return success - 0
858             @return error - error string
859              
860             =head2 send_keep_alive_loop
861              
862             Send keepalive messages to MCP in an endless loop.
863              
864             @param int - sleep time between two keepalives
865              
866             =head2 run
867              
868             Main function of Program Run Control.
869              
870             =head1 AUTHORS
871              
872             =over 4
873              
874             =item *
875              
876             AMD OSRC Tapper Team <tapper@amd64.org>
877              
878             =item *
879              
880             Tapper Team <tapper-ops@amazon.com>
881              
882             =back
883              
884             =head1 COPYRIGHT AND LICENSE
885              
886             This software is Copyright (c) 2019 by Advanced Micro Devices, Inc..
887              
888             This is free software, licensed under:
889              
890             The (two-clause) FreeBSD License
891              
892             =cut