#!/bin/perl use strict; use warnings; use utf8; use Data::Dumper; use Encode 'encode_utf8'; use File::Temp qw/ :mktemp /; use Getopt::Long qw(:config auto_help); use POSIX ":sys_wait_h"; use POSIX qw(strftime); # yaml (quick) parser use YAML::Syck; our @children; our $opt_runfile = "runfile.yml"; my $log_procid = "dtf-controller"; my $log_logfile; my @log_buffer; sub log_any { my $msg = shift; my $where = shift; $msg = "$log_procid: $msg\n"; if (defined $where and $where eq "stderr") { print STDERR $msg; } else { print STDOUT $msg; } # Buffer the output if the log file is not available yet. if (!$log_logfile) { push @log_buffer, $msg; } else { for (@log_buffer) { print { $log_logfile } $_; } @log_buffer = (); print { $log_logfile } $msg; } } sub log_info { my $msg = "info: " . shift; log_any $msg; } sub log_error { my $msg = "error: " . shift; log_any $msg, "stderr"; } sub log_die { my $msg = "fatal: " . shift; log_any $msg, "stderr"; exit (1); } sub load_runfile { open my $fd, '<', $opt_runfile or die "can't open yaml file '$opt_runfile'"; my $config = YAML::Syck::LoadFile($fd) or die "can't parse '$opt_runfile'"; return $config; } sub subcommand { my $cmd = $_[0]; my $out_file = "/dev/null"; my $err_file = "/dev/null"; if (defined $_[1]) { $out_file = $_[1]; } if (defined $_[2]) { $err_file = $_[2]; } log_info "running: " . join(' ', @$cmd); my $pid = fork(); if ($pid eq 0) { open (STDOUT, ">", "$out_file") or die "can not open out log"; open (STDERR, ">", "$err_file") or die "can not open err log"; exec @$cmd or die "can't exec"; } else { $SIG{INT} = 'IGNORE'; wait; return $? >> 8; } } sub child_task { my $run = $_[0]; my $dir = $_[1]; my $config = $_[2]; open ($log_logfile, '>', "$dir/controller.log"); log_info "results into $dir"; my $runfile_err = 0; for (("setup_playbook", "distro_version", "distro", "taskdir")) { my $param = $_; if (!defined $run->{$_}) { log_error "missing parameter '$param' in runfile"; $runfile_err ++; } } log_die "failed, nr. of errors: $runfile_err" if ($runfile_err); my $task = "$run->{distro}-$run->{distro_version}"; $log_procid = "$log_procid($task)"; my $rc = subcommand [ '@bindir@/dtf-run-remote', '--taskdir', $run->{taskdir}, '--workdir', $dir, '--distro', $run->{distro}, '--distro-version', $run->{distro_version}, '--setup-playbook', $run->{setup_playbook}, ], "$dir/dtf-run-remote.out", "$dir/dtf-run-remote.err"; # Note that the 'dtf-run-remote' must return EXIT_SUCCESS even if some of # its tests failed. if ($rc ne 0) { log_error "failed dtf-run-remote"; } my $db = $config->{db}; my $respath = "$run->{distro}/" . "$run->{distro_version}/$run->{arch}/"; my $taskdir = $respath . "results_$config->{starttime}-$run->{stamp}"; $rc = subcommand [ '@libexecdir@/dtf-commit-results', "$dir", "$taskdir", "$db" ]; if ($rc ne 0) { log_error "failed dtf-commit-results"; exit (1); } subcommand [ '@libexecdir@/dtf-result-stats', "$db/$respath", ], "$db/$respath/results.html", "$db/$respath/dtf-result-stats.stderr" and exit(1); } sub main { my $config = load_runfile; log_info "test-description: $config->{description}"; $config->{starttime} = strftime "%Y%m%d_%H%M%S", gmtime; for my $run (@{$config->{runs}}) { # dies on error - which is OK my $dir = File::Temp->newdir("/tmp/dtf-controller-XXXXXX", CLEANUP => 0); ($run->{stamp} = $dir) =~ s/.*-//; my $pid = fork(); if ($pid == 0) { child_task($run, $dir, $config); ## child's successful run ## exit (0); } else { push @children, $pid; } } $SIG{INT} = sub { log_info ".. waiting for children SIGINTing\n"; }; # wait for all pseudo sub-processes my $child; do { sleep (0.5); $child = waitpid (-1, 0); } while ($child > 0); log_info "Finished.."; } GetOptions( "runfile=s", ) || exit (1); main;