diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/Iurt/Chroot.pm | 87 | ||||
-rw-r--r-- | lib/Iurt/Config.pm | 57 | ||||
-rw-r--r-- | lib/Iurt/Emi.pm | 26 | ||||
-rw-r--r-- | lib/Iurt/Process.pm | 59 | ||||
-rw-r--r-- | lib/Iurt/Queue.pm | 112 | ||||
-rw-r--r-- | lib/Iurt/RPM.pm | 55 | ||||
-rwxr-xr-x | lib/Iurt/Ulri.pm | 160 | ||||
-rw-r--r-- | lib/Iurt/Urpmi.pm | 159 | ||||
-rw-r--r-- | lib/Iurt/Util.pm | 2 |
9 files changed, 498 insertions, 219 deletions
diff --git a/lib/Iurt/Chroot.pm b/lib/Iurt/Chroot.pm index 1427aa3..8a2e04b 100644 --- a/lib/Iurt/Chroot.pm +++ b/lib/Iurt/Chroot.pm @@ -96,8 +96,12 @@ sub clean_and_build_chroot { } if ($run->{additional_media} && $run->{additional_media}{repository}) { - _setup_additional_media($run, $config, $chroot) or return; + if (!_setup_additional_media($run, $config, $chroot)) { + _clean_mounts($run, $config, $chroot); + return; + } } + 1; } @@ -232,7 +236,7 @@ sub clean_all_chroot_tmp { } foreach (readdir($dir)) { /$prefix/ or next; - clean_chroot($run, $config, "$chroot_dir/$_"); + clean_chroot("$chroot_dir/$_", $run, $config); } closedir $dir; } @@ -268,12 +272,13 @@ sub check_mounted { } sub check_chroot_need_update { - my ($tmp_chroot, $run) = @_; + my ($tmp_chroot, $run, $config) = @_; - my $tmp_urpmi = mktemp("$tmp_chroot/tmp.XXXXXX"); + sudo($config, '--mkdir', "-m", 01777, "$tmp_chroot/tmp"); + my $tmp_urpmi = mktemp("$tmp_chroot/tmp/tmp.XXXXXX"); mkdir_p("$tmp_urpmi/tmp"); my @installed_pkgs = grep { !/^gpg-pubkey/ } chomp_(cat_("$tmp_chroot/var/log/qa")); - my @available_pkgs = chomp_(`urpmq --urpmi-root $tmp_urpmi --use-distrib $run->{urpmi}{distrib_url} --list -f 2>/dev/null`); + my @available_pkgs = chomp_(`urpmq --urpmi-root $tmp_urpmi --use-distrib $run->{urpmi}{distrib_url} --list -f`); my @removed_pkgs = difference2(\@installed_pkgs, \@available_pkgs); rm_rf($tmp_urpmi); @@ -303,16 +308,6 @@ sub create_build_chroot { $ret = create_build_chroot_tar($chroot, $chroot_ref, $run, $config); } - if ($ret) { - my $urpmi = $run->{urpmi}; - if ($urpmi->{use__urpmi_root} && !$run->{chrooted_urpmi}) { - if (!$urpmi->add_media__urpmi_root($chroot, $config->{base_media})) { - plog('ERROR', "urpmi.addmedia --urpmi-root failed"); - return; - } - } - } - if ($ret && $use_netns) { sudo($config, '--netns_create', $chroot); } @@ -322,47 +317,44 @@ sub create_build_chroot { sub create_build_chroot_tar { my ($chroot, $chroot_tar, $run, $config) = @_; - my $tmp_chroot = mktemp("$chroot.tmp.XXXXXX"); my $rebuild; - my $clean = sub { - plog("Remove temporary chroot"); - sudo($config, '--rm', '-r', $tmp_chroot); - }; plog('NOTIFY', "creating chroot"); - mkdir_p($tmp_chroot); if (!-f $chroot_tar) { plog("rebuild chroot tarball"); $rebuild = 1; } elsif (!$run->{fixed_media}) { - plog('DEBUG', "decompressing /var/log/qa from $chroot_tar in $tmp_chroot"); - sudo($config, '--untar', $chroot_tar, $tmp_chroot, "./var/log/qa"); - $rebuild = check_chroot_need_update($tmp_chroot, $run); + my $tmp_chroot = mktemp("$chroot.tmp.XXXXXX"); + sudo($config, "--mkdir", "-p", "$tmp_chroot"); + plog('DEBUG', "decompressing /var/log/qa from $chroot_tar in $tmp_chroot"); + sudo($config, '--untar', $chroot_tar, $tmp_chroot, "./var/log/qa"); + $rebuild = check_chroot_need_update($tmp_chroot, $run, $config); + sudo($config, '--rm', '-r', $tmp_chroot); } + sudo($config, '--rm', '-r', $chroot); + # Create this directory as root as it will be / of the chroot + sudo($config, "--mkdir", "-p", "$chroot"); if ($rebuild) { - sudo($config, '--rm', '-r', $chroot); - if (!build_chroot($run, $config, $tmp_chroot)) { + if (!build_chroot($run, $config, $chroot)) { plog('NOTIFY', "creating chroot failed."); - $clean->(); + sudo($config, '--rm', '-r', $chroot); return; } - sudo($config, "--tar", $chroot_tar, $tmp_chroot); - # This rename may fail if for example tmp chroots are in another FS - # This does not matter as it will then be rm + untar - rename $tmp_chroot, $chroot; - } - - if (!-d $chroot) { - plog('DEBUG', "recreate chroot $chroot"); - plog('NOTIFY', "recreate chroot"); - mkdir_p $chroot; + plog('NOTIFY', "chroot recreated in $chroot"); + my $tmp_tar = mktemp("$chroot_tar.tmp.XXXXXX"); + sudo($config, "--tar", $chroot_tar, $chroot); + if (rename($tmp_tar, $chroot_tar)) { + plog('NOTIFY', "archive creation failed."); + unlink($tmp_tar); + return; + } + plog('NOTIFY', "chroot archived in $chroot_tar"); + } else { sudo($config, '--untar', $chroot_tar, $chroot); - plog('NOTIFY', "chroot recreated in $chroot_tar (live in $chroot)"); + plog('NOTIFY', "chroot recreated in $chroot (from $chroot_tar)"); } - - $clean->(); 1; } @@ -373,7 +365,7 @@ sub create_build_chroot_btrfs { plog('NOTIFY', "creating btrfs chroot"); # TODO: Handle $run{fixed_media} - if (check_chroot_need_update($chroot_ref, $run)) { + if (check_chroot_need_update($chroot_ref, $run, $config)) { sudo($config, '--btrfs_delete', $chroot_ref); if (!sudo($config, '--btrfs_create', $chroot_ref)) { plog('ERROR', "creating btrfs subvolume failed."); @@ -408,16 +400,13 @@ sub build_chroot { # install chroot my $urpmi = $run->{urpmi}; # perl_checker: $urpmi = Iurt::Urpmi->new - if ($urpmi->{use__urpmi_root}) { - if (!$urpmi->add_media__urpmi_root($tmp_chroot, $config->{base_media})) { - plog('ERROR', "urpmi.addmedia --urpmi-root failed"); - return 0; - } - $urpmi->set_command__urpmi_root($tmp_chroot); - } else { - $urpmi->set_command__use_distrib($tmp_chroot); + if (!$urpmi->add_media__urpmi_root($tmp_chroot, $config->{base_media})) { + plog('ERROR', "urpmi.addmedia --urpmi-root failed"); + return 0; } + $urpmi->set_command($tmp_chroot); + # (blino) install meta-task first for prefer.vendor.list to be used foreach my $packages ([ 'meta-task' ], $config->{basesystem_packages}) { if (!$urpmi->install_packages( diff --git a/lib/Iurt/Config.pm b/lib/Iurt/Config.pm index 32b87e4..3b8bbe1 100644 --- a/lib/Iurt/Config.pm +++ b/lib/Iurt/Config.pm @@ -1,7 +1,6 @@ package Iurt::Config; use base qw(Exporter); -use RPM4::Header; use Data::Dumper; use MDK::Common; use Iurt::Util qw(plog); @@ -17,8 +16,6 @@ our @EXPORT = qw( get_date get_prefix get_author_email - check_arch - check_noarch get_package_prefix get_mandatory_arch get_target_arch @@ -26,15 +23,13 @@ our @EXPORT = qw( ); our %arch_comp = ( - 'i586' => { 'i386' => 1, 'i486' => 1, 'i586' => 1 }, - 'i686' => { 'i386' => 1, 'i486' => 1, 'i586' => 1, 'i686' => 1 }, - 'x86_64' => { 'x86_64' => 1 }, - 'ppc' => { 'ppc' => 1 }, - 'ppc64' => { 'ppc' => 1, 'ppc64' => 1 }, + 'i586' => { 'i386' => 1, 'i486' => 1 }, + 'i686' => { 'i386' => 1, 'i486' => 1, 'i586' => 1 }, + 'ppc64' => { 'ppc' => 1 }, 'armv5tejl' => { 'armv5tl' => 1 }, 'armv5tel' => { 'armv5tl' => 1 }, - 'armv5tl' => { 'armv5tl' => 1 }, - 'armv7l' => { 'armv5tl' => 1, 'armv7hl' => 1, 'armv7hnl' => 1 }, + 'armv7l' => { 'armv5tl' => 1, 'armv7hnl' => 1 }, + 'armv8l' => { 'armv5tl' => 1, 'armv7hl' => 1, 'armv7hnl' => 1 }, ); @@ -151,48 +146,6 @@ sub get_author_email { return $authoremail; } -sub check_noarch { - my ($rpm) = @_; - my $hdr = RPM4::Header->new($rpm); - - # Stupid rpm doesn't return an empty list so we must check for (none) - - my ($build_archs) = $hdr->queryformat('%{BUILDARCHS}'); - - if ($build_archs ne '(none)') { - ($build_archs) = $hdr->queryformat('[%{BUILDARCHS} ]'); - my @list = split ' ', $build_archs; - return 1 if member('noarch', @list); - } - - return 0; -} - -sub check_arch { - my ($rpm, $arch) = @_; - my $hdr = RPM4::Header->new($rpm); - - # Stupid rpm doesn't return an empty list so we must check for (none) - - my ($exclusive_arch) = $hdr->queryformat('%{EXCLUSIVEARCH}'); - - if ($exclusive_arch ne '(none)') { - ($exclusive_arch) = $hdr->queryformat('[%{EXCLUSIVEARCH} ]'); - my @list = split ' ', $exclusive_arch; - return 0 unless member($arch, @list); - } - - my ($exclude_arch) = $hdr->queryformat('[%{EXCLUDEARCH} ]'); - - if ($exclude_arch ne '(none)') { - ($exclude_arch) = $hdr->queryformat('[%{EXCLUDEARCH} ]'); - my @list = split ' ', $exclude_arch; - return 0 if member($arch, @list); - } - - return 1; -} - sub get_mandatory_arch { my ($config, $target) = @_; find { ref($_) eq 'ARRAY' } $config->{mandatory_arch}, diff --git a/lib/Iurt/Emi.pm b/lib/Iurt/Emi.pm index 851307c..bf76a64 100644 --- a/lib/Iurt/Emi.pm +++ b/lib/Iurt/Emi.pm @@ -6,12 +6,14 @@ use Iurt::Config qw(get_author_email get_mandatory_arch get_target_arch); use Iurt::Mail qw(sendmail); use Iurt::Queue qw(check_if_all_archs_processed); use Iurt::Util qw(plog); +use MDK::Common::File qw(touch); use MDK::Common::Func qw(find); use MDK::Common::DataStructure qw(difference2); use strict; our @EXPORT = qw( find_prefixes_ready_to_upload + record_uploaded_packages upload_prefix_in_media ); @@ -150,11 +152,10 @@ sub upload_prefix_in_media { plog('OK', " uploading $rpm in $done/$path"); } - # This should not happen :( + # When a package is not marked as noarch but only has noarch subpackages, there will be nothing + # left to upload when it finishes building on non mandaory arch. return unless @packages; - $user ||= $config->{upload_user}; - my $command = generate_upload_command($prefix, $media, $target, $user, \@packages, $o_finish, "$done$path/$youri_file"); plog('DEBUG', "running $command"); if (!system($command)) { @@ -193,3 +194,22 @@ sub upload_prefix_in_media { } } } + +sub record_uploaded_packages { + my ($config, $pkg_tree, $prefix, $media) = @_; + my $done = "$config->{queue}/done"; + my $path = $pkg_tree->{$prefix}{media}{$media}{path}; + + my %arches; + foreach my $pkg (@{$pkg_tree->{$prefix}{media}{$media}{rpms}}) { + my ($arch) = $pkg =~ /\.([^.]*)\.rpm$/; + $arches{$arch} = 1 unless $arch eq 'src'; + } + # Only keep noarch if it's the only architecture + delete $arches{'noarch'} if 1 < keys %arches; + + foreach my $arch (keys %arches) { + touch("$done/$path/${prefix}_$arch.uploaded"); + } + touch("$done/$path/$prefix.upload"); +} diff --git a/lib/Iurt/Process.pm b/lib/Iurt/Process.pm index 46b8de2..c234d0b 100644 --- a/lib/Iurt/Process.pm +++ b/lib/Iurt/Process.pm @@ -14,8 +14,8 @@ our @EXPORT = qw( clean_process check_pid perform_command - set_alarm_message sudo + wait_for_lock ); my $sudo = '/usr/bin/sudo'; @@ -24,6 +24,7 @@ my $sudo = '/usr/bin/sudo'; Check that there is no other program running and create a pidfile lock I<$run> current running options +I<$exit> whether to exit when the lock is in use Return true. =cut @@ -32,7 +33,7 @@ Return true. # should be designed sub check_pid { - my ($run) = @_; + my ($run, $exit) = @_; my $pidfile = "$run->{pidfile_home}/$run->{pidfile}"; @@ -59,9 +60,7 @@ sub check_pid { if ($time < time()-7200 || $state eq 'Z') { my $i; - my $msg = "another instance [$pid] is too old, killing it"; - set_alarm_message($msg); - plog('WARN', $msg); + plog('WARN', "another instance [$pid] is too old, killing it"); while ($i < 5 && getpgrp $pid != -1) { kill_for_good($pid); @@ -69,9 +68,10 @@ sub check_pid { sleep 1; } } else { - plog('WARN', "another instance [$pid] is already running for ", - time()-$time, " seconds"); - exit(); + plog('WARN', "another instance [$pid] is already running for", + time()-$time, "seconds"); + exit() if $exit; + return; } } else { plog('WARN', "cleaning stale lockfile"); @@ -84,6 +84,20 @@ sub check_pid { $pidfile; } +sub wait_for_lock { + my ($run) = @_; + + plog('INFO', 'Waiting for ulri lock to be available'); + my $delay = 10; + my $pidfile = check_pid($run, 0); + while (!defined($pidfile)) { + plog('WARN', "waiting $delay seconds before retrying"); + sleep $delay; + $pidfile = check_pid($run, 0); + } + + $pidfile; +} sub fork_to_monitor { my ($run, $config, $logfile, %opt) = @_; @@ -105,18 +119,14 @@ sub fork_to_monitor { my (@stat) = stat $logfile; if ($stat[7] > $size_limit) { # FIXME: we left runaway processes (eg: urpmi) - my $msg = "Killing current command because of log size exceeding limit ($stat[7] > $config->{log_size_limit})"; - set_alarm_message($msg); - plog('ERROR', $msg); + plog('ERROR', "Killing current command because of log size exceeding limit ($stat[7] > $config->{log_size_limit})"); kill 14, "-$parent_pid"; exit(); } if ($opt{stalled_timeout} && $stat[9] && $stat[9] + $opt{stalled_timeout} < time()) { # If nothing was written to the logfile for more than stalled_timeout, check if the system seems busy if ((getload())[1] < 0.5) { - my $msg = "Killing current command because it seems blocked"; - set_alarm_message($msg); - plog('ERROR', $msg); + plog('ERROR', "Killing current command because it seems blocked"); kill 14, "-$parent_pid"; exit(); } @@ -125,8 +135,7 @@ sub fork_to_monitor { my $df = df $opt{log}; if ($df->{per} >= 99) { # FIXME: we left runaway processes (eg: urpmi) - set_alarm_message(my $msg = "Killing current command because running out of disk space at $opt{log} (only $df->{bavail}KB left)"); - plog('ERROR', $msg); + plog('ERROR', "Killing current command because running out of disk space at $opt{log} (only $df->{bavail}KB left)"); kill 14, "-$parent_pid"; exit(); } @@ -192,7 +201,7 @@ sub handle_wait_regexp { sub generate_comment { my ($run, $config, $output, $command, $comment, $pipe, $kill, %opt) = @_; if ($kill && $opt{type} ne 'shell') { - $comment = "Command killed after $opt{timeout}s: $command\n"; + $comment = "Command killed: $command\n"; my ($cmd_to_kill) = $command =~ /sudo(?: chroot \S+)? (.*)/; clean_process($cmd_to_kill); } elsif ($pipe) { @@ -210,13 +219,6 @@ sub generate_comment { } } -my $alarm_message; - -sub set_alarm_message { - my ($msg) = @_; - $alarm_message = $msg; -} - =head2 perform_command($command, $run, $config, %opt) Run a command and check various running parameters such as log size, timeout... @@ -258,19 +260,18 @@ sub perform_command { my $retry = $opt{retry} || 1; my $call_ret = 1; my ($err, $try); - my $logfile = "$opt{log}/$opt{logname}.$run->{run}.log"; + my $logfile = "$opt{log}/$opt{logname}.$run->{my_arch}.$run->{run}.log"; my $max_retry = max($config->{max_command_retry}, $retry); while ($retry) { $try++; - $logfile = "$opt{log}/$opt{logname}-$try.$run->{run}.log" if $opt{retry} > 1; + $logfile = "$opt{log}/$opt{logname}-$try.$run->{my_arch}.$run->{run}.log" if $opt{retry} > 1; my $pid = $opt{log} ? fork_to_monitor($run, $config, $logfile, %opt) : 0; eval { # handle timeout: - set_alarm_message("Timeout! (timeout was $opt{timeout})"); local $SIG{ALRM} = sub { - print $alarm_message, "\n"; + print "Killed! (probably because of the $opt{timeout} timeout)\n"; $kill = 1; die "alarm\n"; # NB: \n required }; @@ -306,7 +307,6 @@ sub perform_command { $err = 0 if any { $_ == $err } @{$opt{error_ok}}; # kill pid watching log file size - set_alarm_message("kill pid watching log file size"); kill_for_good($pid) if $pid; if ($perl_err) { # timed out @@ -382,7 +382,6 @@ sub kill_for_good { my ($pid) = @_; # try SIGALARM first: - set_alarm_message("Killing current command because it seems blocked"); kill 14, $pid; sleep 1; waitpid(-1, POSIX::WNOHANG); diff --git a/lib/Iurt/Queue.pm b/lib/Iurt/Queue.pm index 3930ca4..e78bd39 100644 --- a/lib/Iurt/Queue.pm +++ b/lib/Iurt/Queue.pm @@ -3,8 +3,9 @@ package Iurt::Queue; use base qw(Exporter); use File::Copy 'move'; use File::Path 'make_path'; +use File::stat 'stat'; use Iurt::Config qw(get_mandatory_arch get_target_arch); -use Iurt::File qw(read_line); +use Iurt::File qw(read_line create_file); use Iurt::Util qw(plog); use MDK::Common qw(cat_ find member partition); use strict; @@ -14,6 +15,10 @@ our @EXPORT = qw( cleanup_failed_build check_if_all_archs_processed check_if_mandatory_arch_failed + load_lock_file_data + record_bot_complete + remove_bot_from_package + schedule_next_retry ); sub apply_to_upload_tree { @@ -145,6 +150,51 @@ sub cleanup_failed_build { } } +sub load_lock_file_data { + my ($ent, $lock_path, $media, $config) = @_; + if ($lock_path !~ /\/(\d{14}\.\w+\.\w+\.\d+)_([\w-]+)\.(\w+)\.([\w-]+)\.(\d{14})\.(\d+)\.lock$/) { + plog('ERROR', "invalid lock file name: $lock_path"); + return; + } + my ($prefix, $arch, $bot, $host, $date, $pid) = ($1, $2, $3, $4, $5, $6); + + $arch = $config->{arch_translation}{$arch} if $config->{arch_translation}{$arch}; + plog('DEBUG', "found lock on $host/$arch for $prefix"); + + if ($arch =~ /noarch/) { + plog('DEBUG', "... and $prefix is noarch"); + $ent->{media}{$media}{arch}{noarch} = 1; + $arch =~ s/-.*//; + } + + $ent->{media}{$media}{arch}{$arch} = 1; + + my $time = read_line($lock_path); + $time = (split ' ', $time)[2]; + push @{$ent->{media}{$media}{bot}}, { + bot => $bot, + host => $host, + date => $date, + pid => $pid, + 'arch' => $arch, + 'time' => $time + }; +} + +sub remove_bot_from_package { + my ($ent, $media, $host, $pid) = @_; + @{$ent->{media}{$media}{bot}} = grep { $_->{host} ne $host || $_->{pid} != $pid} @{$ent->{media}{$media}{bot}}; +} + +sub record_bot_complete { + my ($run, $bot, $arch, $lock_file, $prefix, $ent, $media, $host, $pid) = @_; + plog('INFO', "delete lock file for $prefix on $host/$arch"); + unlink $lock_file; + $run->{bot}{$host}{$bot} = 0; + remove_bot_from_package($ent, $media, $host, $pid); + $ent->{media}{$media}{arch}{$arch} = 0; +} + sub get_upload_tree_state { our ($config) = @_; @@ -170,33 +220,13 @@ sub get_upload_tree_state { $pkg_tree{$prefix}{srpm_name}{$name} = $srpm; } - if ($r =~ /(\d{14}\.\w+\.\w+\.\d+)_([\w-]+)\.(\w+)\.(\w+)\.(\d{14})\.(\d+)\.lock$/) { - my ($prefix, $arch, $bot, $host, $date, $pid) = ($1, $2, $3, $4, $5, $6); + if ($r =~ /(\d{14}\.\w+\.\w+\.\d+)_([\w-]+)\.(\w+)\.([\w-]+)\.(\d{14})\.(\d+)\.lock$/) { + my $prefix = $1; # Set path here too has we may have a lock without the src.rpm $pkg_tree{$prefix}{media}{$media}{path} = "/$f/$m/$s"; - $arch = $config->{arch_translation}{$arch} if $config->{arch_translation}{$arch}; - plog('DEBUG', "found lock on $host/$arch for $prefix"); - - if ($arch =~ /noarch/) { - plog('DEBUG', "... and $prefix is noarch"); - $pkg_tree{$prefix}{media}{$media}{arch}{noarch} = 1; - $arch =~ s/-.*//; - } - - $pkg_tree{$prefix}{media}{$media}{arch}{$arch} = 1; - - my $time = read_line("$todo/$f/$m/$s/$r"); - $time = (split ' ', $time)[2]; - push @{$pkg_tree{$prefix}{media}{$media}{bot}}, { - bot => $bot, - host => $host, - date => $date, - pid => $pid, - 'arch' => $arch, - 'time' => $time - }; + load_lock_file_data(\%{$pkg_tree{$prefix}}, "$todo/$f/$m/$s/$r", $media, $config); } if ($r =~ /(\d{14}\.\w+\.\w+\.\d+)_.*\.deps$/) { @@ -206,6 +236,20 @@ sub get_upload_tree_state { $pkg_tree{$prefix}{deps} = \@deps; } + + if ($r =~ /(\d{14}\.\w+\.\w+\.\d+)_(.*)\.retry$/) { + my $prefix = $1; + my $arch = $2; + my $mtime = stat("$todo/$f/$m/$s/$r")->mtime; + my $nb_failures = cat_("$todo/$f/$m/$s/$r"); + plog('DEBUG', "$prefix failed $nb_failures times, last one was as " . localtime($mtime)); + if ($mtime > time) { + plog('INFO', "Too early to retry $prefix, waiting until " . localtime($mtime)); + $pkg_tree{$prefix}{media}{$media}{later}{$arch} = 1; + } else { + $pkg_tree{$prefix}{media}{$media}{retries}{arch}{nb_failures} = $nb_failures; + } + } } sub done_func { @@ -238,6 +282,8 @@ sub get_upload_tree_state { $pkg_tree{$prefix}{media}{$media}{failed_arch}{$arch} = 1; } elsif ($result eq 'cancelled') { $pkg_tree{$prefix}{media}{$media}{cancelled_arch}{$arch} = 1; + } elsif ($result eq 'uploaded') { + $pkg_tree{$prefix}{media}{$media}{uploaded_arch}{$arch} = 1; } else { plog('WARNING', "unknown state $arch.$result for $prefix"); } @@ -256,3 +302,21 @@ sub get_upload_tree_state { return %pkg_tree; } + +sub schedule_next_retry { + my ($config, $todo_dir, $prefix, $arch, $nb_failures) = @_; + + # If backoff_delays is not set, do nothing and retry forever + return 1 unless defined $config->{backoff_delays}; + + my $backoff_delays = $config->{backoff_delays}; + my $file = "$todo_dir/${prefix}_$arch.retry"; + create_file($file, $nb_failures+1); + if ($nb_failures >= scalar(@$backoff_delays)) { + plog('INFO', "$prefix failed too many times with a retriable error ($nb_failures)"); + return; + } + my $mtime = time + @$backoff_delays[$nb_failures]; + utime(time, $mtime, $file); + return 1; +} diff --git a/lib/Iurt/RPM.pm b/lib/Iurt/RPM.pm new file mode 100644 index 0000000..de09ba4 --- /dev/null +++ b/lib/Iurt/RPM.pm @@ -0,0 +1,55 @@ +package Iurt::RPM; + +use base qw(Exporter); +use RPM4::Header; +use MDK::Common; +use strict; + +our @EXPORT = qw( + check_arch + check_noarch +); + +sub check_noarch { + my ($rpm) = @_; + my $hdr = RPM4::Header->new($rpm); + + # Stupid rpm doesn't return an empty list so we must check for (none) + + my ($build_archs) = $hdr->queryformat('%{BUILDARCHS}'); + + if ($build_archs ne '(none)') { + ($build_archs) = $hdr->queryformat('[%{BUILDARCHS} ]'); + my @list = split ' ', $build_archs; + return 1 if member('noarch', @list); + } + + return 0; +} + +sub check_arch { + my ($rpm, $arch) = @_; + my $hdr = RPM4::Header->new($rpm); + + # Stupid rpm doesn't return an empty list so we must check for (none) + + my ($exclusive_arch) = $hdr->queryformat('%{EXCLUSIVEARCH}'); + + if ($exclusive_arch ne '(none)') { + ($exclusive_arch) = $hdr->queryformat('[%{EXCLUSIVEARCH} ]'); + my @list = split ' ', $exclusive_arch; + return 0 unless member($arch, @list); + } + + my ($exclude_arch) = $hdr->queryformat('[%{EXCLUDEARCH} ]'); + + if ($exclude_arch ne '(none)') { + ($exclude_arch) = $hdr->queryformat('[%{EXCLUDEARCH} ]'); + my @list = split ' ', $exclude_arch; + return 0 if member($arch, @list); + } + + return 1; +} + +1; diff --git a/lib/Iurt/Ulri.pm b/lib/Iurt/Ulri.pm index ce9d104..09be6b0 100755 --- a/lib/Iurt/Ulri.pm +++ b/lib/Iurt/Ulri.pm @@ -3,18 +3,155 @@ package Iurt::Ulri; use base qw(Exporter); use File::Path qw(make_path); use File::Temp qw(mktemp); -use Iurt::Config qw(get_author_email); +use Iurt::Config qw(config_init config_usage get_author_email); use Iurt::File qw(check_file_timeout); use Iurt::Mail qw(sendmail); -use Iurt::Util qw(plog ssh_setup ssh sput); +use Iurt::Util qw(plog ssh_setup sget ssh sput); use File::Slurp qw(read_file); +use MDK::Common qw(cat_); use strict; our @EXPORT = qw( build_package + fetch_logs_and_cleanup + load_config warn_about_failure ); +sub load_config { + my ($run) = @_; + my $HOME = $ENV{HOME}; + my $configfile = "$HOME/.upload.conf"; + my $sysconfigfile = "/etc/iurt/upload.conf"; + + my $config = {}; + foreach my $f ($configfile, $sysconfigfile) { + plog('DEBUG', "load config: $f"); + if (-f $f) { + $config = eval(cat_($f)) + or die "FATAL $run->{program_name}: syntax error in $f"; + last; + } + } + + my %config_usage = ( + admin => { + desc => 'mail address of the bot administrator', + default => 'distrib-admin@mandrivalinux.org' + }, + 'arch_translation' => { + desc => "Renaming of arch", + default => { 'sparc64' => 'sparcv9' } + }, + bot => { + desc => "List of bot able to compile the packages", + default => { + i586 => { + localhost => { + iurt => { + user => 'builder', + command => qq(iurt --copy_srpm --group --config local_spool /home/builder/iurt/__DIR__ --no_rsync --chrooted-urpmi -m __MEDIA__ -- http://localhost/distrib/ -p "__PACKAGER__" -r __TARGET__ __ARCH__), + packages => '/home/builder/iurt/', + }, + }, + }, + }, + }, + media => { + desc => 'Corresponding media to add given the current media', + default => { + default => { + "core/backports" => [ "core/backports", "core/release", "core/updates" ], + "core/backports_testing" => [ + "core/backports", "core/backports_testing", + "core/release", "core/updates" + ], + "core/release" => [ "core/release" ], + "core/updates" => [ "core/release", "core/updates" ], + "core/updates_testing" => [ + "core/release", "core/updates", "core/updates_testing" + ], + "nonfree/backports" => [ + "core/backports", "core/release", "core/updates", + "nonfree/backports", "nonfree/release", "nonfree/updates" + ], + "nonfree/backports_testing" => [ + "core/backports", "core/backports_testing", + "core/release", "core/updates", + "nonfree/backports", "nonfree/backports_testing", + "nonfree/release", "nonfree/updates" + ], + "nonfree/release" => [ "core/release", "nonfree/release" ], + "nonfree/updates" => [ + "core/release", "core/updates", + "nonfree/release", "nonfree/updates" + ], + "nonfree/updates_testing" => [ + "core/release", "core/updates", "core/updates_testing", + "nonfree/release", "nonfree/updates", "nonfree/updates_testing" + ], + "tainted/backports" => [ + "core/backports", "core/release", "core/updates", + "tainted/backports", "tainted/release", "tainted/updates" + ], + "tainted/backports_testing" => [ + "core/backports", "core/backports_testing", + "core/release", "core/updates", + "tainted/backports", "tainted/backports_testing", + "tainted/release", "tainted/updates" + ], + "tainted/release" => [ "core/release", "tainted/release" ], + "tainted/updates" => [ + "core/release", "core/updates", + "tainted/release", "tainted/updates" + ], + "tainted/updates_testing" => [ + "core/release", "core/updates", "core/updates_testing", + "tainted/release", "tainted/updates", "tainted/updates_testing" + ], + }, + }, + }, + faildelay => { + desc => "Time after which the rebuild is considered as a failure", + default => 36000 + }, + http_queue => { + desc => 'Address where log can be consulted', + default => 'http://pkgsubmit.mageia.org/uploads/' + }, + queue => { + desc => "Root of the tree where the packages to compile are located", + default => "$HOME/uploads" + }, + tmp => { + desc => "Temporary directory", + default => "$HOME/tmp" + }, + ssh_options => { + desc => "SSH options", + default => "-o ConnectTimeout=5 -o BatchMode=yes -o ServerAliveInterval=5" + }, + packager => { + desc => 'Default packager tag user by bot', + default => 'Mageia Team <http://www.mageia.org>' + }, + 'arch' => { + desc => 'Architectures list for each target', + default => { + default => [ 'i586', 'x86_64' ], + }, + }, + 'backoff_delays' => { + desc => 'List of delays in seconds before retrying retriable errors. Error becomes permanent after reaching the end of the list.', + default => [5*60, 30*60, 60*60, 120*60] + }, + ); + config_usage(\%config_usage, $config) if $run->{config_usage}; + config_init(\%config_usage, $config, $run); + return $config; +} + sub build_package { my ($config, $pkg_tree, $media, $prefix, $host, $arch, $bot) = @_; @@ -80,13 +217,13 @@ sub build_package { plog('DEBUG', "Will compile only with media $media_to_add"); $cmd =~ s!__MEDIA__!$media_to_add!g; - #- allow x86_64 hosts to build i586 packages - if ($arch eq 'i586') { - $cmd = "setarch i586 $cmd"; + #- force 32bit if needed, this allows to build 32 bits package on 64 bit hosts + if ($arch =~ /^(i.86|armv5tl|armv7hl)/) { + $cmd = "setarch linux32 $cmd"; } plog('DEBUG', "Build $pkgs"); - ssh($remote, "'echo PID=\$\$; exec $cmd $pkgs &>$prefix_dir/log/botcmd.\$(date +%s).\$(hostname -s).log' > $temp &"); + ssh($remote, "'echo PID=\$\$; exec $cmd $pkgs &>$prefix_dir/log/botcmd.\$(date +%s).$arch.\$(hostname -s).log' > $temp &"); # wait 10 seconds or until we have the log file # plus 20 seconds if it timeouts. @@ -144,6 +281,13 @@ sub get_pid_from_file { $pid; } +sub fetch_logs_and_cleanup { + my ($remote, $remote_dir, $target_dir) = @_; + make_path($target_dir); + sget($remote, "$remote_dir/log/*", $target_dir); + ssh($remote, "rm -rf $remote_dir"); +} + sub warn_about_failure { my ($config, $user, $ent, $arch, $fail_dir, $path, $prefix) = @_; my $text = join("\n", "Build of the following packages failed:\n", map { "- $_" } @{$ent->{srpms}}) . "\n"; @@ -161,11 +305,11 @@ sub warn_about_failure { $text .= "\nLog files generated:\n"; opendir my $DP1, "$fail_dir/$prefix/log/"; - foreach my $f1 (sort(readdir($DP1))) { + foreach my $f1 (readdir($DP1)) { next if ! -d "$fail_dir/$prefix/log/$f1" || $f1 =~ m/^\./; opendir my $DP2, "$fail_dir/$prefix/log/$f1"; - foreach my $f2 (readdir $DP2) { + foreach my $f2 (sort(readdir $DP2)) { next if $f2 =~ m/^\./; $text .= "$fpath/log/$f1/$f2\n"; } diff --git a/lib/Iurt/Urpmi.pm b/lib/Iurt/Urpmi.pm index 7ac6a74..53d0756 100644 --- a/lib/Iurt/Urpmi.pm +++ b/lib/Iurt/Urpmi.pm @@ -5,6 +5,7 @@ use RPM4::Header; use File::Basename; use File::NCopy qw(copy); use MDV::Distribconf::Build; +use MDK::Common qw(cat_); use Iurt::Chroot qw(add_local_user create_temp_chroot); use Iurt::Process qw(perform_command clean_process sudo); use Iurt::Config qw(get_package_prefix); @@ -21,31 +22,15 @@ sub new { my $config = $self->{config}; my $run = $self->{run}; - if ($run->{chrooted_urpmi}) { - $run->{chrooted_media} = $run->{chrooted_urpmi}{rooted_media} . - "/$run->{distro}/$run->{my_arch}"; - - # Now squash all slashes that don't follow colon - $run->{chrooted_media} =~ s|(?<!:)/+|/|g; - - plog('DEBUG', "installation media: $run->{chrooted_media}"); - } - $self->{use__urpmi_root} = !urpm::is_local_url($config->{repository}); $self->{distrib_url} = "$config->{repository}/$run->{distro}/$run->{my_arch}"; $self; } -sub set_command__urpmi_root { +sub set_command { my ($self, $chroot_tmp) = @_; - $self->{use_iurt_root_command} = 1; $self->{urpmi_command} = "urpmi $self->{urpmi_options} --urpmi-root $chroot_tmp"; } -sub set_command__use_distrib { - my ($self, $chroot_tmp) = @_; - $self->{use_iurt_root_command} = 1; - $self->{urpmi_command} = "urpmi $self->{urpmi_options} --use-distrib $self->{distrib_url} --root $chroot_tmp"; -} sub set_local_media { my ($self, $local_media) = @_; @@ -65,10 +50,7 @@ sub urpmi_command { my $run = $self->{run}; my $local_media = $self->{local_media}; - if ($run->{chrooted_urpmi}) { - # FIXME chrooted_urpmi should always use urpmi_root and not support local media - $self->{use__urpmi_root} ? &set_command__urpmi_root : &set_command__use_distrib; - + &set_command; # Here should be added only the needed media for the given package # main/release -> main/release # main/testing -> main/release main/testing @@ -82,18 +64,17 @@ sub urpmi_command { my $m_name = $m; $m_name =~ s,/,_,g; if (!add_media($self, $chroot_tmp, $m_name, - "$m_name $run->{chrooted_media}/media/$m")) { + "$m_name $self->{distrib_url}/media/$m")) { $run->{chrooted_urpmi} = 0; - plog('ERROR', "Failed to add media $m_name. Disabling chrooted_urpmi."); + plog('ERROR', "Failed to add media $m_name."); return; } } } else { # FIXME Do not hardcode Core - if (!add_media($self, $chroot_tmp, 'Core', "--distrib $run->{chrooted_media}")) { - if (!add_media($self, $chroot_tmp, 'Core', "--wget --distrib $run->{chrooted_media}")) { - $run->{chrooted_urpmi} = 0; - plog('ERROR', "Failed to add media $run->{chrooted_media}. Disabling chrooted_urpmi."); + if (!add_media($self, $chroot_tmp, 'Core', "--distrib $self->{distrib_url}")) { + if (!add_media($self, $chroot_tmp, 'Core', "--wget --distrib $self->{distrib_url}")) { + plog('ERROR', "Failed to add media $self->{distrib_url}."); return; } } @@ -149,9 +130,6 @@ sub urpmi_command { } return 1; - } else { - &set_command__use_distrib; - } } sub check_media_added { @@ -188,12 +166,9 @@ sub add_media { my $run = $self->{run}; my $config = $self->{config}; - plog("add chroot media: $run->{chrooted_media}"); + plog("add chroot media: $self->{distrib_url}"); - my $cmd = "chroot $chroot urpmi.addmedia $media"; - if ($run->{chrooted_urpmi}) { - $cmd = "urpmi-addmedia -v --urpmi-root $chroot $media"; - } + my $cmd = "urpmi-addmedia -v --urpmi-root $chroot $media"; perform_command($cmd, $run, $config, mail => $config->{admin}, @@ -208,6 +183,24 @@ sub add_media { 1; } +sub update { + my ($self, $chroot) = @_; + my $run = $self->{run}; + my $config = $self->{config}; + + plog('INFO', "updating packages in $chroot"); + + my $cmd = "urpmi --urpmi-root $chroot --auto-update --auto"; + perform_command($cmd, + $run, $config, + mail => $config->{admin}, + retry => 2, + use_iurt_root_command => 1, + debug_mail => $run->{debug}) + or return; + 1; +} + sub get_local_provides { my ($self) = @_; my $run = $self->{run}; @@ -341,8 +334,6 @@ sub _install_callback { # it seems the is needed urpmi error is due to something else (likely a # database corruption error). # my @missing_deps = $output =~ /(?:(\S+) is needed by )|(?:\(due to unsatisfied ([^[ ]*)(?: (.*)|\[(.*)\])?\))/g; - # - my @missing_deps = $output =~ /([^ \n]+) \(due to unsatisfied ([^[ \n]*)(?: ([^\n]*)|\[([^\n]*)\])?\)/g; # <mrl> 20071106 FIXME: This should not be needed anymore @@ -392,13 +383,13 @@ sub install_packages { @to_install or return 1; - (my $log_dirname = $title) =~ s/.*:(.*)\.src.rpm/$1/; + (my $log_dirname = $title) =~ s/^[^:]*:?([^:]*)\.(buildreqs\.nosrc|src).rpm/$1/; my $log_spool = "$local_spool/log/$log_dirname/"; mkdir $log_spool; - my @rpm = grep { !/\.src\.rpm$/ } @to_install; + my @rpm = grep { !/src\.rpm$/ } @to_install; if ($opt->{check} && -f "$chroot_tmp/bin/rpm" @@ -415,7 +406,7 @@ sub install_packages { if (!perform_command( join(' ', $self->{urpmi_command}, @options, @to_install), $run, $config, - use_iurt_root_command => $self->{use_iurt_root_command}, + use_iurt_root_command => 1, error => $error, logname => $log, hash => "${log}_$title", @@ -452,16 +443,16 @@ sub install_packages { $ok; } -sub clean_urpmi_process { - my ($self) = @_; - my $run = $self->{run}; - my $program_name = $run->{program_name}; - if (!$run->{chrooted_urpmi}) { - my $match = $self->{urpmi_command} or return; - if (!clean_process($match)) { - die "FATAL $program_name: Could not have urpmi working !"; - } +sub get_srpm_name { + my ($run, $config, $chroot_tmp, $srpm, $luser, $spec) = @_; + if (!perform_command(qq(chroot $chroot_tmp su $luser -c "rpmspec -q --qf %{NVR}.src.rpm --srpm /home/$luser/rpmbuild/SPECS/$spec > /home/$luser/rpmbuild/SPECS/$spec.srpm_name"), + $run, $config, + use_iurt_root_command => 1, + hash => "identify_$srpm")) { + plog("ERROR: failed to get the name of the generated src.rpm"); + return; } + return cat_("$chroot_tmp/home/$luser/rpmbuild/SPECS/$spec.srpm_name"); } # return ("exit_code", srpm, spec) @@ -518,7 +509,7 @@ sub recreate_srpm { my $filelist = `rpm -qlp $oldsrpm`; my ($name) = $srpm =~ /(?:.*:)?(.*)-[^-]+-[^-]+\.src\.rpm$/; foreach my $file (split "\n", $filelist) { - if ($file =~ /(.*)\.spec/) { + if ($file =~ /(.*)\.spec$/) { if (!$spec) { $spec = $file; } elsif ($1 eq $name) { @@ -545,8 +536,12 @@ sub recreate_srpm { # return 0 unless $ret; - my $file = (glob "$chroot_tmp/home/$luser/rpmbuild/SRPMS/$name-*.src.rpm")[0]; - my ($new_srpm) = basename($file); + my $new_srpm = get_srpm_name($run, $config, $chroot_tmp, $srpm, $luser, $spec); + if (!$new_srpm) { + plog("ERROR: failed to get the name of the generated src.rpm"); + return; + } + my $file = "$chroot_tmp/home/$luser/rpmbuild/SRPMS/$new_srpm"; my $prefix = get_package_prefix($srpm); my $newfile = "$chroot_tmp/home/$luser/rpmbuild/SRPMS/$prefix$new_srpm"; if (-f $file && $newfile ne $file) { @@ -561,4 +556,64 @@ sub recreate_srpm { ($ret, "$prefix$new_srpm", $spec); } +sub install_dynamic_buildrequires { + my ($self, $run, $config, $chroot_tmp, $luser, $spec, $srpm) = @_; + my $program_name = $run->{program_name}; + my $with_flags = $run->{with_flags}; + + plog('INFO', "handling dynamic buildrequires"); + + if (`rpm -qp --requires "$chroot_tmp/home/$luser/rpmbuild/SRPMS/$srpm"` !~ /rpmlib\(DynamicBuildRequires\)/) { + plog('DEBUG', "DynamicBuildRequires not required for $srpm"); + return 1; + } + + my $new_srpm = get_srpm_name($run, $config, $chroot_tmp, $srpm, $luser, $spec); + if (!$new_srpm) { + plog("ERROR: failed to get the name of the generated src.rpm"); + return; + } + my $nosrc = $new_srpm =~ s/src.rpm$/buildreqs.nosrc.rpm/r; + + while (1) { + # There is no way with perform_command to get the actual error code. + # We can however tell it that error 11 (missing BuildRequires) is ok so + # that we get an error if it fails for any other reason. + my $ret = perform_command(qq(chroot $chroot_tmp su $luser -c "rpmbuild -br $with_flags /home/$luser/rpmbuild/SPECS/$spec"), + $run, $config, + use_iurt_root_command => 1, + hash => "generatebuildrequires_$srpm" + ); + return 1 if $ret; + + # Unfortunately iurt_root_command hides the original error code so + # we need to find out if the file was created rather than relying on + # the command exiting with 11. + if (-f "$chroot_tmp/home/$luser/rpmbuild/SRPMS/$nosrc") { + plog("INFO: Some dynamic BuildRequires are missing"); + my @packages = ("$chroot_tmp/home/$luser/rpmbuild/SRPMS/$nosrc"); + if (!$self->install_packages( + $srpm, + $chroot_tmp, + $run->{local_spool}, + 'dynamic_buildrequires', + "[DYNAMIC BUILDREQUIRES] installation of dynamic BuildRequires failed for $srpm on $run->{my_arch}", + {}, + @packages + )) { + plog('ERROR', "Failed to install dynamic BuildRequires."); + return 0; + } + # TODO: Have a max retry counter for safety + # Delete the file if it exists so that we know if it got created again + unlink("$chroot_tmp/home/$luser/rpmbuild/SRPMS/$nosrc"); + } else { + # This was a failure for another reason, no point retrying + plog('ERROR', "Failed to generate dynamic BuildRequires."); + $run->{status}{$srpm} = 'build_failure'; + return 0; + } + } +} + 1; diff --git a/lib/Iurt/Util.pm b/lib/Iurt/Util.pm index f9dfa9f..bf726d0 100644 --- a/lib/Iurt/Util.pm +++ b/lib/Iurt/Util.pm @@ -167,7 +167,7 @@ execute I<@command>. Return the command output. sub sout { my $conf = shift; my ($opt, $user, $host) = @$conf; - `ssh $opt -x $user\@$host @_ 2>/dev/null`; + `ssh $opt -x $user\@$host "@_" 2>/dev/null`; } =item sget($handle, $from, $to) |