2008-11-25 11:01:42 +00:00
|
|
|
|
package Hydra::Helper::Nix;
|
2008-11-18 14:48:40 +00:00
|
|
|
|
|
|
|
|
|
use strict;
|
2021-08-19 16:36:43 -04:00
|
|
|
|
use warnings;
|
2008-11-28 14:36:04 +00:00
|
|
|
|
use Exporter;
|
2009-02-06 21:01:20 +00:00
|
|
|
|
use File::Path;
|
2009-01-13 14:02:07 +00:00
|
|
|
|
use File::Basename;
|
2013-05-09 14:29:04 +02:00
|
|
|
|
use Config::General;
|
2021-07-26 03:43:09 +00:00
|
|
|
|
use Hydra::Config;
|
2010-09-03 09:17:52 +00:00
|
|
|
|
use Hydra::Helper::CatalystUtils;
|
2012-03-13 12:10:19 +01:00
|
|
|
|
use Hydra::Model::DB;
|
2013-10-04 17:01:47 +02:00
|
|
|
|
use Nix::Store;
|
2014-08-13 18:53:29 +02:00
|
|
|
|
use Encode;
|
2015-11-17 11:03:05 +01:00
|
|
|
|
use Sys::Hostname::Long;
|
2017-02-21 16:17:31 +01:00
|
|
|
|
use IPC::Run;
|
2008-11-28 14:36:04 +00:00
|
|
|
|
|
|
|
|
|
our @ISA = qw(Exporter);
|
2009-02-06 15:02:49 +00:00
|
|
|
|
our @EXPORT = qw(
|
2020-04-10 18:13:36 +02:00
|
|
|
|
getHydraHome getHydraConfig getBaseUrl
|
2016-02-06 03:44:16 +01:00
|
|
|
|
getSCMCacheDir getStatsdConfig
|
2009-03-15 11:56:11 +00:00
|
|
|
|
registerRoot getGCRootsDir gcRootFor
|
2013-11-05 16:05:29 +01:00
|
|
|
|
jobsetOverview jobsetOverview_
|
2017-04-05 17:55:56 +02:00
|
|
|
|
getDrvLogPath findLog
|
2013-02-21 17:27:17 +01:00
|
|
|
|
getMainOutput
|
2013-04-02 23:32:04 +02:00
|
|
|
|
getEvals getMachines
|
2013-05-25 15:36:58 -04:00
|
|
|
|
pathIsInsidePrefix
|
2013-09-21 14:47:52 +00:00
|
|
|
|
captureStdoutStderr run grab
|
2017-10-18 12:23:07 +02:00
|
|
|
|
getTotalShares
|
|
|
|
|
getStoreUri
|
|
|
|
|
readNixFile
|
|
|
|
|
isLocalStore
|
2013-10-04 17:01:47 +02:00
|
|
|
|
cancelBuilds restartBuilds);
|
2008-11-18 14:48:40 +00:00
|
|
|
|
|
|
|
|
|
|
2012-02-28 20:16:16 +01:00
|
|
|
|
sub getHydraHome {
|
|
|
|
|
my $dir = $ENV{"HYDRA_HOME"} or die "The HYDRA_HOME directory does not exist!\n";
|
|
|
|
|
return $dir;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2015-12-14 13:31:24 +01:00
|
|
|
|
my $hydraConfig;
|
|
|
|
|
|
2013-01-22 13:19:28 +01:00
|
|
|
|
sub getHydraConfig {
|
2015-12-14 13:31:24 +01:00
|
|
|
|
return $hydraConfig if defined $hydraConfig;
|
2012-03-13 12:10:19 +01:00
|
|
|
|
my $conf = $ENV{"HYDRA_CONFIG"} || (Hydra::Model::DB::getHydraPath . "/hydra.conf");
|
2021-07-26 17:11:21 +00:00
|
|
|
|
my %opts = (%Hydra::Config::configGeneralOpts, -ConfigFile => $conf);
|
2015-12-14 13:31:24 +01:00
|
|
|
|
if (-f $conf) {
|
2021-08-19 16:25:21 -04:00
|
|
|
|
my %h = Config::General->new(%opts)->getall;
|
2016-06-19 16:59:24 +01:00
|
|
|
|
|
2015-12-14 13:31:24 +01:00
|
|
|
|
$hydraConfig = \%h;
|
|
|
|
|
} else {
|
|
|
|
|
$hydraConfig = {};
|
|
|
|
|
}
|
|
|
|
|
return $hydraConfig;
|
2011-03-07 15:06:32 +00:00
|
|
|
|
}
|
|
|
|
|
|
2008-11-28 14:36:04 +00:00
|
|
|
|
|
2016-02-06 03:44:16 +01:00
|
|
|
|
# Return hash of statsd configuration of the following shape:
|
|
|
|
|
# (
|
|
|
|
|
# host => string,
|
|
|
|
|
# port => digit
|
|
|
|
|
# )
|
|
|
|
|
sub getStatsdConfig {
|
|
|
|
|
my ($config) = @_;
|
|
|
|
|
my $cfg = $config->{statsd};
|
|
|
|
|
my %statsd = defined $cfg ? ref $cfg eq "HASH" ? %$cfg : ($cfg) : ();
|
|
|
|
|
|
|
|
|
|
return {
|
2021-08-19 17:01:27 -04:00
|
|
|
|
"host" => $statsd{'host'} // 'localhost',
|
|
|
|
|
"port" => $statsd{'port'} // 8125,
|
2016-02-06 03:44:16 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-18 15:09:54 -04:00
|
|
|
|
sub getHydraNotifyPrometheusConfig {
|
|
|
|
|
my ($config) = @_;
|
|
|
|
|
my $cfg = $config->{hydra_notify};
|
|
|
|
|
|
2021-08-24 11:35:38 -04:00
|
|
|
|
if (!defined($cfg)) {
|
|
|
|
|
return undef;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (ref $cfg ne "HASH") {
|
|
|
|
|
print STDERR "Error reading Hydra's configuration file: hydra_notify should be a block.\n";
|
2021-08-18 15:09:54 -04:00
|
|
|
|
return undef;
|
|
|
|
|
}
|
|
|
|
|
|
2021-09-06 22:03:44 -04:00
|
|
|
|
my $promcfg = $cfg->{prometheus};
|
|
|
|
|
if (!defined($promcfg)) {
|
2021-08-24 11:35:38 -04:00
|
|
|
|
return undef;
|
|
|
|
|
}
|
|
|
|
|
|
2021-09-06 22:03:44 -04:00
|
|
|
|
if (ref $promcfg ne "HASH") {
|
2021-08-24 11:35:38 -04:00
|
|
|
|
print STDERR "Error reading Hydra's configuration file: hydra_notify.prometheus should be a block.\n";
|
2021-08-18 15:09:54 -04:00
|
|
|
|
return undef;
|
|
|
|
|
}
|
|
|
|
|
|
2021-09-06 22:03:44 -04:00
|
|
|
|
if (defined($promcfg->{"listen_address"}) && defined($promcfg->{"port"})) {
|
2021-08-18 15:09:54 -04:00
|
|
|
|
return {
|
2021-09-06 22:03:44 -04:00
|
|
|
|
"listen_address" => $promcfg->{'listen_address'},
|
|
|
|
|
"port" => $promcfg->{'port'},
|
2021-08-18 15:09:54 -04:00
|
|
|
|
};
|
2021-08-24 11:35:38 -04:00
|
|
|
|
} else {
|
|
|
|
|
print STDERR "Error reading Hydra's configuration file: hydra_notify.prometheus should include listen_address and port.\n";
|
|
|
|
|
return undef;
|
2021-08-18 15:09:54 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return undef;
|
|
|
|
|
}
|
|
|
|
|
|
2016-02-06 03:44:16 +01:00
|
|
|
|
|
2015-11-17 11:03:05 +01:00
|
|
|
|
sub getBaseUrl {
|
|
|
|
|
my ($config) = @_;
|
|
|
|
|
return $config->{'base_uri'} // "http://" . hostname_long . ":3000";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2013-05-25 15:36:58 -04:00
|
|
|
|
sub getSCMCacheDir {
|
|
|
|
|
return Hydra::Model::DB::getHydraPath . "/scm" ;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2009-02-06 21:01:20 +00:00
|
|
|
|
sub getGCRootsDir {
|
2015-12-14 13:31:24 +01:00
|
|
|
|
my $config = getHydraConfig();
|
|
|
|
|
my $dir = $config->{gc_roots_dir};
|
|
|
|
|
unless (defined $dir) {
|
|
|
|
|
die unless defined $ENV{LOGNAME};
|
|
|
|
|
$dir = ($ENV{NIX_STATE_DIR} || "/nix/var/nix" ) . "/gcroots/per-user/$ENV{LOGNAME}/hydra-roots";
|
|
|
|
|
}
|
2009-03-15 11:56:11 +00:00
|
|
|
|
mkpath $dir if !-e $dir;
|
|
|
|
|
return $dir;
|
2009-02-06 21:01:20 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2009-03-15 11:56:11 +00:00
|
|
|
|
sub gcRootFor {
|
2009-02-06 21:01:20 +00:00
|
|
|
|
my ($path) = @_;
|
2009-03-31 15:59:47 +00:00
|
|
|
|
return getGCRootsDir . "/" . basename $path;
|
2009-03-15 11:56:11 +00:00
|
|
|
|
}
|
2009-02-06 21:01:20 +00:00
|
|
|
|
|
|
|
|
|
|
2009-03-15 11:56:11 +00:00
|
|
|
|
sub registerRoot {
|
|
|
|
|
my ($path) = @_;
|
|
|
|
|
my $link = gcRootFor $path;
|
2014-08-13 16:29:00 +02:00
|
|
|
|
return if -e $link;
|
2021-10-19 22:37:17 -04:00
|
|
|
|
open(my $root, ">", $link) or die "cannot create GC root `$link' to `$path'";
|
2021-09-06 22:19:00 -04:00
|
|
|
|
close $root;
|
2009-02-06 21:01:20 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2013-11-05 16:05:29 +01:00
|
|
|
|
sub jobsetOverview_ {
|
|
|
|
|
my ($c, $jobsets) = @_;
|
|
|
|
|
return $jobsets->search({},
|
2021-03-25 18:16:17 -04:00
|
|
|
|
{ order_by => ["hidden ASC", "enabled DESC", "name"]
|
2012-06-26 12:00:18 +02:00
|
|
|
|
, "+select" =>
|
2021-06-01 11:19:33 -04:00
|
|
|
|
[ "(select count(*) from Builds as a where me.id = a.jobset_id and a.finished = 0 and a.isCurrent = 1)"
|
|
|
|
|
, "(select count(*) from Builds as a where me.id = a.jobset_id and a.finished = 1 and buildstatus <> 0 and a.isCurrent = 1)"
|
|
|
|
|
, "(select count(*) from Builds as a where me.id = a.jobset_id and a.finished = 1 and buildstatus = 0 and a.isCurrent = 1)"
|
|
|
|
|
, "(select count(*) from Builds as a where me.id = a.jobset_id and a.isCurrent = 1)"
|
2011-08-19 15:13:34 +00:00
|
|
|
|
]
|
2013-01-22 14:09:37 +01:00
|
|
|
|
, "+as" => ["nrscheduled", "nrfailed", "nrsucceeded", "nrtotal"]
|
2012-02-29 02:22:49 +01:00
|
|
|
|
});
|
2010-09-03 09:17:52 +00:00
|
|
|
|
}
|
2009-10-20 12:35:01 +00:00
|
|
|
|
|
2012-03-05 21:52:47 +01:00
|
|
|
|
|
2013-11-05 16:05:29 +01:00
|
|
|
|
sub jobsetOverview {
|
|
|
|
|
my ($c, $project) = @_;
|
|
|
|
|
my $jobsets = $project->jobsets->search(isProjectOwner($c, $project) ? {} : { hidden => 0 });
|
|
|
|
|
return jobsetOverview_($c, $jobsets);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2013-01-22 22:48:02 +01:00
|
|
|
|
# Return the path of the build log of the given derivation, or undef
|
|
|
|
|
# if the log is gone.
|
|
|
|
|
sub getDrvLogPath {
|
|
|
|
|
my ($drvPath) = @_;
|
|
|
|
|
my $base = basename $drvPath;
|
2013-02-13 13:09:07 +01:00
|
|
|
|
my $bucketed = substr($base, 0, 2) . "/" . substr($base, 2);
|
2017-04-05 17:55:56 +02:00
|
|
|
|
my $fn = Hydra::Model::DB::getHydraPath . "/build-logs/";
|
|
|
|
|
for ($fn . $bucketed, $fn . $bucketed . ".bz2") {
|
2015-06-09 14:21:21 +02:00
|
|
|
|
return $_ if -f $_;
|
2013-02-13 13:09:07 +01:00
|
|
|
|
}
|
2013-01-22 22:48:02 +01:00
|
|
|
|
return undef;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2013-08-30 13:53:25 +00:00
|
|
|
|
# Find the log of the derivation denoted by $drvPath. It it doesn't
|
|
|
|
|
# exist, try other derivations that produced its outputs (@outPaths).
|
|
|
|
|
sub findLog {
|
|
|
|
|
my ($c, $drvPath, @outPaths) = @_;
|
|
|
|
|
|
|
|
|
|
if (defined $drvPath) {
|
2013-08-30 18:11:03 +00:00
|
|
|
|
my $logPath = getDrvLogPath($drvPath);
|
|
|
|
|
return $logPath if defined $logPath;
|
2013-08-30 13:53:25 +00:00
|
|
|
|
}
|
2013-10-04 17:01:47 +02:00
|
|
|
|
|
2013-08-30 13:53:25 +00:00
|
|
|
|
return undef if scalar @outPaths == 0;
|
|
|
|
|
|
|
|
|
|
my @steps = $c->model('DB::BuildSteps')->search(
|
2013-08-30 18:11:03 +00:00
|
|
|
|
{ path => { -in => [@outPaths] } },
|
|
|
|
|
{ select => ["drvpath"]
|
2013-08-30 13:53:25 +00:00
|
|
|
|
, distinct => 1
|
2013-08-30 18:11:03 +00:00
|
|
|
|
, join => "buildstepoutputs"
|
|
|
|
|
});
|
2013-08-30 13:53:25 +00:00
|
|
|
|
|
|
|
|
|
foreach my $step (@steps) {
|
2013-09-03 14:43:08 -04:00
|
|
|
|
next unless defined $step->drvpath;
|
2013-08-30 18:11:03 +00:00
|
|
|
|
my $logPath = getDrvLogPath($step->drvpath);
|
|
|
|
|
return $logPath if defined $logPath;
|
2013-08-30 13:53:25 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return undef;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2013-02-13 16:49:28 +00:00
|
|
|
|
sub getMainOutput {
|
|
|
|
|
my ($build) = @_;
|
|
|
|
|
return
|
|
|
|
|
$build->buildoutputs->find({name => "out"}) //
|
|
|
|
|
$build->buildoutputs->find({}, {limit => 1, order_by => ["name"]});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2013-02-26 19:08:39 +01:00
|
|
|
|
sub getEvalInputs {
|
|
|
|
|
my ($c, $eval) = @_;
|
|
|
|
|
my @inputs = $eval->jobsetevalinputs->search(
|
|
|
|
|
{ -or => [ -and => [ uri => { '!=' => undef }, revision => { '!=' => undef }], dependency => { '!=' => undef }], altNr => 0 },
|
|
|
|
|
{ order_by => "name" });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
sub getEvalInfo {
|
|
|
|
|
my ($cache, $eval) = @_;
|
|
|
|
|
my $res = $cache->{$eval->id}; return $res if defined $res;
|
|
|
|
|
|
|
|
|
|
# Get stats for this eval.
|
|
|
|
|
my $nrScheduled;
|
|
|
|
|
my $nrSucceeded = $eval->nrsucceeded;
|
|
|
|
|
if (defined $nrSucceeded) {
|
|
|
|
|
$nrScheduled = 0;
|
|
|
|
|
} else {
|
|
|
|
|
$nrScheduled = $eval->builds->search({finished => 0})->count;
|
|
|
|
|
$nrSucceeded = $eval->builds->search({finished => 1, buildStatus => 0})->count;
|
|
|
|
|
if ($nrScheduled == 0) {
|
|
|
|
|
$eval->update({nrsucceeded => $nrSucceeded});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Get the inputs.
|
|
|
|
|
my @inputsList = $eval->jobsetevalinputs->search(
|
|
|
|
|
{ -or => [ -and => [ uri => { '!=' => undef }, revision => { '!=' => undef }], dependency => { '!=' => undef }], altNr => 0 },
|
|
|
|
|
{ order_by => "name" });
|
|
|
|
|
my $inputs;
|
|
|
|
|
$inputs->{$_->name} = $_ foreach @inputsList;
|
|
|
|
|
|
|
|
|
|
return $cache->{$eval->id} =
|
|
|
|
|
{ nrScheduled => $nrScheduled
|
|
|
|
|
, nrSucceeded => $nrSucceeded
|
|
|
|
|
, inputs => $inputs
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2021-06-16 12:42:25 -04:00
|
|
|
|
=head2 getEvals
|
|
|
|
|
|
|
|
|
|
This method returns a list of evaluations with details about what changed,
|
|
|
|
|
intended to be used with `eval.tt`.
|
|
|
|
|
|
|
|
|
|
Arguments:
|
|
|
|
|
|
|
|
|
|
=over 4
|
|
|
|
|
|
|
|
|
|
=item C<$c>
|
|
|
|
|
L<Hydra> - the entire application.
|
|
|
|
|
|
|
|
|
|
=item C<$evals_result_set>
|
|
|
|
|
|
|
|
|
|
A L<DBIx::Class::ResultSet> for the result class of L<Hydra::Model::DB::JobsetEvals>
|
|
|
|
|
|
|
|
|
|
=item C<$offset>
|
|
|
|
|
|
|
|
|
|
Integer offset when selecting evaluations
|
|
|
|
|
|
|
|
|
|
=item C<$rows>
|
|
|
|
|
|
|
|
|
|
Integer rows to fetch
|
|
|
|
|
|
|
|
|
|
=back
|
|
|
|
|
|
|
|
|
|
=cut
|
2013-02-21 17:27:17 +01:00
|
|
|
|
sub getEvals {
|
2021-06-16 12:42:25 -04:00
|
|
|
|
my ($c, $evals_result_set, $offset, $rows) = @_;
|
2013-02-21 17:27:17 +01:00
|
|
|
|
|
2021-06-16 12:42:25 -04:00
|
|
|
|
my $me = $evals_result_set->current_source_alias;
|
2021-06-16 11:22:25 -04:00
|
|
|
|
|
2021-06-16 12:42:25 -04:00
|
|
|
|
my @evals = $evals_result_set->search(
|
2013-02-21 17:27:17 +01:00
|
|
|
|
{ hasnewbuilds => 1 },
|
2021-06-16 11:22:25 -04:00
|
|
|
|
{ order_by => "$me.id DESC", rows => $rows, offset => $offset
|
|
|
|
|
, prefetch => { evaluationerror => [ ] } });
|
2013-02-21 17:27:17 +01:00
|
|
|
|
my @res = ();
|
2013-02-26 19:08:39 +01:00
|
|
|
|
my $cache = {};
|
|
|
|
|
|
|
|
|
|
foreach my $curEval (@evals) {
|
|
|
|
|
|
|
|
|
|
my ($prevEval) = $c->model('DB::JobsetEvals')->search(
|
2021-01-26 09:50:59 -05:00
|
|
|
|
{ jobset_id => $curEval->get_column('jobset_id')
|
2013-02-26 19:08:39 +01:00
|
|
|
|
, hasnewbuilds => 1, id => { '<', $curEval->id } },
|
|
|
|
|
{ order_by => "id DESC", rows => 1 });
|
|
|
|
|
|
|
|
|
|
my $curInfo = getEvalInfo($cache, $curEval);
|
2021-09-07 21:35:01 -04:00
|
|
|
|
my $prevInfo;
|
|
|
|
|
$prevInfo = getEvalInfo($cache, $prevEval) if defined $prevEval;
|
2013-02-21 17:27:17 +01:00
|
|
|
|
|
|
|
|
|
# Compute what inputs changed between each eval.
|
|
|
|
|
my @changedInputs;
|
2015-02-25 13:13:12 +01:00
|
|
|
|
foreach my $input (sort { $a->name cmp $b->name } values(%{$curInfo->{inputs}})) {
|
2013-02-26 19:08:39 +01:00
|
|
|
|
my $p = $prevInfo->{inputs}->{$input->name};
|
|
|
|
|
push @changedInputs, $input if
|
|
|
|
|
!defined $p
|
|
|
|
|
|| ($input->revision || "") ne ($p->revision || "")
|
|
|
|
|
|| $input->type ne $p->type
|
|
|
|
|
|| ($input->uri || "") ne ($p->uri || "")
|
|
|
|
|
|| ($input->get_column('dependency') || "") ne ($p->get_column('dependency') || "");
|
2013-02-21 17:27:17 +01:00
|
|
|
|
}
|
2013-02-26 19:08:39 +01:00
|
|
|
|
|
|
|
|
|
push @res,
|
|
|
|
|
{ eval => $curEval
|
|
|
|
|
, nrScheduled => $curInfo->{nrScheduled}
|
|
|
|
|
, nrSucceeded => $curInfo->{nrSucceeded}
|
|
|
|
|
, nrFailed => $curEval->nrbuilds - $curInfo->{nrSucceeded} - $curInfo->{nrScheduled}
|
|
|
|
|
, diff => defined $prevEval ? $curInfo->{nrSucceeded} - $prevInfo->{nrSucceeded} : 0
|
2013-02-21 17:27:17 +01:00
|
|
|
|
, changedInputs => [ @changedInputs ]
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2013-02-26 19:08:39 +01:00
|
|
|
|
return [@res];
|
2013-02-21 17:27:17 +01:00
|
|
|
|
}
|
|
|
|
|
|
2014-11-19 14:59:36 +01:00
|
|
|
|
|
2013-03-04 15:37:20 -05:00
|
|
|
|
sub getMachines {
|
|
|
|
|
my %machines = ();
|
2015-08-25 14:11:50 +02:00
|
|
|
|
|
|
|
|
|
my @machinesFiles = split /:/, ($ENV{"NIX_REMOTE_SYSTEMS"} || "/etc/nix/machines");
|
|
|
|
|
|
|
|
|
|
for my $machinesFile (@machinesFiles) {
|
|
|
|
|
next unless -e $machinesFile;
|
2021-10-19 22:37:17 -04:00
|
|
|
|
open(my $conf, "<", $machinesFile) or die;
|
2021-09-07 21:53:07 -04:00
|
|
|
|
while (my $line = <$conf>) {
|
2013-03-04 15:37:20 -05:00
|
|
|
|
chomp;
|
|
|
|
|
s/\#.*$//g;
|
|
|
|
|
next if /^\s*$/;
|
2021-09-07 21:53:07 -04:00
|
|
|
|
my @tokens = split /\s/, $line;
|
2013-03-04 15:37:20 -05:00
|
|
|
|
my @supportedFeatures = split(/,/, $tokens[5] || "");
|
|
|
|
|
my @mandatoryFeatures = split(/,/, $tokens[6] || "");
|
|
|
|
|
$machines{$tokens[0]} =
|
|
|
|
|
{ systemTypes => [ split(/,/, $tokens[1]) ]
|
|
|
|
|
, sshKeys => $tokens[2]
|
|
|
|
|
, maxJobs => int($tokens[3])
|
|
|
|
|
, speedFactor => 1.0 * (defined $tokens[4] ? int($tokens[4]) : 1)
|
|
|
|
|
, supportedFeatures => [ @supportedFeatures, @mandatoryFeatures ]
|
|
|
|
|
, mandatoryFeatures => [ @mandatoryFeatures ]
|
|
|
|
|
};
|
|
|
|
|
}
|
2021-09-06 22:19:00 -04:00
|
|
|
|
close $conf;
|
2013-03-04 15:37:20 -05:00
|
|
|
|
}
|
2015-08-25 14:11:50 +02:00
|
|
|
|
|
2013-03-04 15:37:20 -05:00
|
|
|
|
return \%machines;
|
|
|
|
|
}
|
|
|
|
|
|
2013-02-21 17:27:17 +01:00
|
|
|
|
|
2013-04-02 23:32:04 +02:00
|
|
|
|
# Check whether ‘$path’ is inside ‘$prefix’. In particular, it checks
|
|
|
|
|
# that resolving symlink components of ‘$path’ never takes us outside
|
|
|
|
|
# of ‘$prefix’. We use this to check that Nix build products don't
|
|
|
|
|
# refer to things outside of the Nix store (e.g. /etc/passwd) or to
|
|
|
|
|
# symlinks outside of the store that point into the store
|
|
|
|
|
# (e.g. /run/current-system). Return undef or the resolved path.
|
|
|
|
|
sub pathIsInsidePrefix {
|
|
|
|
|
my ($path, $prefix) = @_;
|
|
|
|
|
my $n = 0;
|
|
|
|
|
$path =~ s/\/+/\//g; # remove redundant slashes
|
|
|
|
|
$path =~ s/\/*$//; # remove trailing slashes
|
|
|
|
|
|
|
|
|
|
return undef unless $path eq $prefix || substr($path, 0, length($prefix) + 1) eq "$prefix/";
|
|
|
|
|
|
|
|
|
|
my @cs = File::Spec->splitdir(substr($path, length($prefix) + 1));
|
|
|
|
|
my $cur = $prefix;
|
|
|
|
|
|
|
|
|
|
foreach my $c (@cs) {
|
|
|
|
|
next if $c eq ".";
|
|
|
|
|
|
|
|
|
|
# ‘..’ should not take us outside of the prefix.
|
|
|
|
|
if ($c eq "..") {
|
|
|
|
|
return if length($cur) <= length($prefix);
|
|
|
|
|
$cur =~ s/\/[^\/]*$// or die; # remove last component
|
|
|
|
|
next;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
my $new = "$cur/$c";
|
|
|
|
|
if (-l $new) {
|
|
|
|
|
my $link = readlink $new or return undef;
|
|
|
|
|
$new = substr($link, 0, 1) eq "/" ? $link : "$cur/$link";
|
|
|
|
|
$new = pathIsInsidePrefix($new, $prefix);
|
|
|
|
|
return undef unless defined $new;
|
|
|
|
|
}
|
|
|
|
|
$cur = $new;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $cur;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2013-05-25 15:36:58 -04:00
|
|
|
|
sub captureStdoutStderr {
|
|
|
|
|
my ($timeout, @cmd) = @_;
|
|
|
|
|
my $stdin = "";
|
|
|
|
|
my $stdout;
|
|
|
|
|
my $stderr;
|
|
|
|
|
|
|
|
|
|
eval {
|
|
|
|
|
local $SIG{ALRM} = sub { die "timeout\n" }; # NB: \n required
|
|
|
|
|
alarm $timeout;
|
|
|
|
|
IPC::Run::run(\@cmd, \$stdin, \$stdout, \$stderr);
|
|
|
|
|
alarm 0;
|
Improve handling of Perl's block eval errors
Taken from `Perl::Critic`:
A common idiom in perl for dealing with possible errors is to use `eval`
followed by a check of `$@`/`$EVAL_ERROR`:
eval {
...
};
if ($EVAL_ERROR) {
...
}
There's a problem with this: the value of `$EVAL_ERROR` (`$@`) can change
between the end of the `eval` and the `if` statement. The issue are object
destructors:
package Foo;
...
sub DESTROY {
...
eval { ... };
...
}
package main;
eval {
my $foo = Foo->new();
...
};
if ($EVAL_ERROR) {
...
}
Assuming there are no other references to `$foo` created, when the
`eval` block in `main` is exited, `Foo::DESTROY()` will be invoked,
regardless of whether the `eval` finished normally or not. If the `eval`
in `main` fails, but the `eval` in `Foo::DESTROY()` succeeds, then
`$EVAL_ERROR` will be empty by the time that the `if` is executed.
Additional issues arise if you depend upon the exact contents of
`$EVAL_ERROR` and both `eval`s fail, because the messages from both will
be concatenated.
Even if there isn't an `eval` directly in the `DESTROY()` method code,
it may invoke code that does use `eval` or otherwise affects
`$EVAL_ERROR`.
The solution is to ensure that, upon normal exit, an `eval` returns a
true value and to test that value:
# Constructors are no problem.
my $object = eval { Class->new() };
# To cover the possiblity that an operation may correctly return a
# false value, end the block with "1":
if ( eval { something(); 1 } ) {
...
}
eval {
...
1;
}
or do {
# Error handling here
};
Unfortunately, you can't use the `defined` function to test the result;
`eval` returns an empty string on failure.
Various modules have been written to take some of the pain out of
properly localizing and checking `$@`/`$EVAL_ERROR`. For example:
use Try::Tiny;
try {
...
} catch {
# Error handling here;
# The exception is in $_/$ARG, not $@/$EVAL_ERROR.
}; # Note semicolon.
"But we don't use DESTROY() anywhere in our code!" you say. That may be
the case, but do any of the third-party modules you use have them? What
about any you may use in the future or updated versions of the ones you
already use?
2020-05-26 10:56:24 +02:00
|
|
|
|
1;
|
|
|
|
|
} or do {
|
2013-05-25 15:36:58 -04:00
|
|
|
|
die unless $@ eq "timeout\n"; # propagate unexpected errors
|
2016-10-06 17:18:10 +02:00
|
|
|
|
return (-1, $stdout, ($stderr // "") . "timeout\n");
|
Improve handling of Perl's block eval errors
Taken from `Perl::Critic`:
A common idiom in perl for dealing with possible errors is to use `eval`
followed by a check of `$@`/`$EVAL_ERROR`:
eval {
...
};
if ($EVAL_ERROR) {
...
}
There's a problem with this: the value of `$EVAL_ERROR` (`$@`) can change
between the end of the `eval` and the `if` statement. The issue are object
destructors:
package Foo;
...
sub DESTROY {
...
eval { ... };
...
}
package main;
eval {
my $foo = Foo->new();
...
};
if ($EVAL_ERROR) {
...
}
Assuming there are no other references to `$foo` created, when the
`eval` block in `main` is exited, `Foo::DESTROY()` will be invoked,
regardless of whether the `eval` finished normally or not. If the `eval`
in `main` fails, but the `eval` in `Foo::DESTROY()` succeeds, then
`$EVAL_ERROR` will be empty by the time that the `if` is executed.
Additional issues arise if you depend upon the exact contents of
`$EVAL_ERROR` and both `eval`s fail, because the messages from both will
be concatenated.
Even if there isn't an `eval` directly in the `DESTROY()` method code,
it may invoke code that does use `eval` or otherwise affects
`$EVAL_ERROR`.
The solution is to ensure that, upon normal exit, an `eval` returns a
true value and to test that value:
# Constructors are no problem.
my $object = eval { Class->new() };
# To cover the possiblity that an operation may correctly return a
# false value, end the block with "1":
if ( eval { something(); 1 } ) {
...
}
eval {
...
1;
}
or do {
# Error handling here
};
Unfortunately, you can't use the `defined` function to test the result;
`eval` returns an empty string on failure.
Various modules have been written to take some of the pain out of
properly localizing and checking `$@`/`$EVAL_ERROR`. For example:
use Try::Tiny;
try {
...
} catch {
# Error handling here;
# The exception is in $_/$ARG, not $@/$EVAL_ERROR.
}; # Note semicolon.
"But we don't use DESTROY() anywhere in our code!" you say. That may be
the case, but do any of the third-party modules you use have them? What
about any you may use in the future or updated versions of the ones you
already use?
2020-05-26 10:56:24 +02:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return ($?, $stdout, $stderr);
|
2013-05-25 15:36:58 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2013-08-12 17:25:59 +02:00
|
|
|
|
sub run {
|
|
|
|
|
my (%args) = @_;
|
|
|
|
|
my $res = { stdout => "", stderr => "" };
|
|
|
|
|
my $stdin = "";
|
|
|
|
|
|
|
|
|
|
eval {
|
|
|
|
|
local $SIG{ALRM} = sub { die "timeout\n" }; # NB: \n required
|
|
|
|
|
alarm $args{timeout} if defined $args{timeout};
|
|
|
|
|
my @x = ($args{cmd}, \$stdin, \$res->{stdout});
|
|
|
|
|
push @x, \$res->{stderr} if $args{grabStderr} // 1;
|
|
|
|
|
IPC::Run::run(@x,
|
2017-04-01 10:38:39 -04:00
|
|
|
|
init => sub {
|
|
|
|
|
chdir $args{dir} or die "changing to $args{dir}" if defined $args{dir};
|
|
|
|
|
if (defined $args{env}) {
|
|
|
|
|
foreach my $key (keys %{$args{env}}) {
|
|
|
|
|
if (defined $args{env}->{$key}) {
|
|
|
|
|
$ENV{$key} = $args{env}->{$key};
|
|
|
|
|
} else {
|
|
|
|
|
delete $ENV{$key};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
2013-08-12 17:25:59 +02:00
|
|
|
|
alarm 0;
|
Improve handling of Perl's block eval errors
Taken from `Perl::Critic`:
A common idiom in perl for dealing with possible errors is to use `eval`
followed by a check of `$@`/`$EVAL_ERROR`:
eval {
...
};
if ($EVAL_ERROR) {
...
}
There's a problem with this: the value of `$EVAL_ERROR` (`$@`) can change
between the end of the `eval` and the `if` statement. The issue are object
destructors:
package Foo;
...
sub DESTROY {
...
eval { ... };
...
}
package main;
eval {
my $foo = Foo->new();
...
};
if ($EVAL_ERROR) {
...
}
Assuming there are no other references to `$foo` created, when the
`eval` block in `main` is exited, `Foo::DESTROY()` will be invoked,
regardless of whether the `eval` finished normally or not. If the `eval`
in `main` fails, but the `eval` in `Foo::DESTROY()` succeeds, then
`$EVAL_ERROR` will be empty by the time that the `if` is executed.
Additional issues arise if you depend upon the exact contents of
`$EVAL_ERROR` and both `eval`s fail, because the messages from both will
be concatenated.
Even if there isn't an `eval` directly in the `DESTROY()` method code,
it may invoke code that does use `eval` or otherwise affects
`$EVAL_ERROR`.
The solution is to ensure that, upon normal exit, an `eval` returns a
true value and to test that value:
# Constructors are no problem.
my $object = eval { Class->new() };
# To cover the possiblity that an operation may correctly return a
# false value, end the block with "1":
if ( eval { something(); 1 } ) {
...
}
eval {
...
1;
}
or do {
# Error handling here
};
Unfortunately, you can't use the `defined` function to test the result;
`eval` returns an empty string on failure.
Various modules have been written to take some of the pain out of
properly localizing and checking `$@`/`$EVAL_ERROR`. For example:
use Try::Tiny;
try {
...
} catch {
# Error handling here;
# The exception is in $_/$ARG, not $@/$EVAL_ERROR.
}; # Note semicolon.
"But we don't use DESTROY() anywhere in our code!" you say. That may be
the case, but do any of the third-party modules you use have them? What
about any you may use in the future or updated versions of the ones you
already use?
2020-05-26 10:56:24 +02:00
|
|
|
|
$res->{status} = $?;
|
|
|
|
|
chomp $res->{stdout} if $args{chomp} // 0;
|
2013-08-12 17:25:59 +02:00
|
|
|
|
|
Improve handling of Perl's block eval errors
Taken from `Perl::Critic`:
A common idiom in perl for dealing with possible errors is to use `eval`
followed by a check of `$@`/`$EVAL_ERROR`:
eval {
...
};
if ($EVAL_ERROR) {
...
}
There's a problem with this: the value of `$EVAL_ERROR` (`$@`) can change
between the end of the `eval` and the `if` statement. The issue are object
destructors:
package Foo;
...
sub DESTROY {
...
eval { ... };
...
}
package main;
eval {
my $foo = Foo->new();
...
};
if ($EVAL_ERROR) {
...
}
Assuming there are no other references to `$foo` created, when the
`eval` block in `main` is exited, `Foo::DESTROY()` will be invoked,
regardless of whether the `eval` finished normally or not. If the `eval`
in `main` fails, but the `eval` in `Foo::DESTROY()` succeeds, then
`$EVAL_ERROR` will be empty by the time that the `if` is executed.
Additional issues arise if you depend upon the exact contents of
`$EVAL_ERROR` and both `eval`s fail, because the messages from both will
be concatenated.
Even if there isn't an `eval` directly in the `DESTROY()` method code,
it may invoke code that does use `eval` or otherwise affects
`$EVAL_ERROR`.
The solution is to ensure that, upon normal exit, an `eval` returns a
true value and to test that value:
# Constructors are no problem.
my $object = eval { Class->new() };
# To cover the possiblity that an operation may correctly return a
# false value, end the block with "1":
if ( eval { something(); 1 } ) {
...
}
eval {
...
1;
}
or do {
# Error handling here
};
Unfortunately, you can't use the `defined` function to test the result;
`eval` returns an empty string on failure.
Various modules have been written to take some of the pain out of
properly localizing and checking `$@`/`$EVAL_ERROR`. For example:
use Try::Tiny;
try {
...
} catch {
# Error handling here;
# The exception is in $_/$ARG, not $@/$EVAL_ERROR.
}; # Note semicolon.
"But we don't use DESTROY() anywhere in our code!" you say. That may be
the case, but do any of the third-party modules you use have them? What
about any you may use in the future or updated versions of the ones you
already use?
2020-05-26 10:56:24 +02:00
|
|
|
|
1;
|
|
|
|
|
} or do {
|
2013-08-12 17:25:59 +02:00
|
|
|
|
die unless $@ eq "timeout\n"; # propagate unexpected errors
|
|
|
|
|
$res->{status} = -1;
|
|
|
|
|
$res->{stderr} = "timeout\n";
|
Improve handling of Perl's block eval errors
Taken from `Perl::Critic`:
A common idiom in perl for dealing with possible errors is to use `eval`
followed by a check of `$@`/`$EVAL_ERROR`:
eval {
...
};
if ($EVAL_ERROR) {
...
}
There's a problem with this: the value of `$EVAL_ERROR` (`$@`) can change
between the end of the `eval` and the `if` statement. The issue are object
destructors:
package Foo;
...
sub DESTROY {
...
eval { ... };
...
}
package main;
eval {
my $foo = Foo->new();
...
};
if ($EVAL_ERROR) {
...
}
Assuming there are no other references to `$foo` created, when the
`eval` block in `main` is exited, `Foo::DESTROY()` will be invoked,
regardless of whether the `eval` finished normally or not. If the `eval`
in `main` fails, but the `eval` in `Foo::DESTROY()` succeeds, then
`$EVAL_ERROR` will be empty by the time that the `if` is executed.
Additional issues arise if you depend upon the exact contents of
`$EVAL_ERROR` and both `eval`s fail, because the messages from both will
be concatenated.
Even if there isn't an `eval` directly in the `DESTROY()` method code,
it may invoke code that does use `eval` or otherwise affects
`$EVAL_ERROR`.
The solution is to ensure that, upon normal exit, an `eval` returns a
true value and to test that value:
# Constructors are no problem.
my $object = eval { Class->new() };
# To cover the possiblity that an operation may correctly return a
# false value, end the block with "1":
if ( eval { something(); 1 } ) {
...
}
eval {
...
1;
}
or do {
# Error handling here
};
Unfortunately, you can't use the `defined` function to test the result;
`eval` returns an empty string on failure.
Various modules have been written to take some of the pain out of
properly localizing and checking `$@`/`$EVAL_ERROR`. For example:
use Try::Tiny;
try {
...
} catch {
# Error handling here;
# The exception is in $_/$ARG, not $@/$EVAL_ERROR.
}; # Note semicolon.
"But we don't use DESTROY() anywhere in our code!" you say. That may be
the case, but do any of the third-party modules you use have them? What
about any you may use in the future or updated versions of the ones you
already use?
2020-05-26 10:56:24 +02:00
|
|
|
|
};
|
2013-08-12 17:25:59 +02:00
|
|
|
|
|
|
|
|
|
return $res;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
sub grab {
|
|
|
|
|
my (%args) = @_;
|
|
|
|
|
my $res = run(%args, grabStderr => 0);
|
2019-03-17 23:15:24 -07:00
|
|
|
|
if ($res->{status}) {
|
|
|
|
|
my $msgloc = "(in an indeterminate location)";
|
|
|
|
|
if (defined $args{dir}) {
|
|
|
|
|
$msgloc = "in $args{dir}";
|
|
|
|
|
}
|
|
|
|
|
die "command `@{$args{cmd}}' failed with exit status $res->{status} $msgloc";
|
|
|
|
|
}
|
2013-08-12 17:25:59 +02:00
|
|
|
|
return $res->{stdout};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2013-09-21 14:47:52 +00:00
|
|
|
|
sub getTotalShares {
|
|
|
|
|
my ($db) = @_;
|
|
|
|
|
return $db->resultset('Jobsets')->search(
|
2013-10-11 12:01:52 +02:00
|
|
|
|
{ 'project.enabled' => 1, 'me.enabled' => { '!=' => 0 } },
|
2013-09-21 14:47:52 +00:00
|
|
|
|
{ join => 'project', select => { sum => 'schedulingshares' }, as => 'sum' })->single->get_column('sum');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2021-10-20 11:33:50 -04:00
|
|
|
|
sub cancelBuilds {
|
2013-10-04 15:40:43 +02:00
|
|
|
|
my ($db, $builds) = @_;
|
2020-04-10 18:13:36 +02:00
|
|
|
|
return $db->txn_do(sub {
|
2015-07-07 14:08:46 +02:00
|
|
|
|
$builds = $builds->search({ finished => 0 });
|
2013-10-04 15:40:43 +02:00
|
|
|
|
my $n = $builds->count;
|
|
|
|
|
my $time = time();
|
|
|
|
|
$builds->update(
|
|
|
|
|
{ finished => 1,
|
|
|
|
|
, iscachedbuild => 0, buildstatus => 4 # = cancelled
|
|
|
|
|
, starttime => $time
|
|
|
|
|
, stoptime => $time
|
|
|
|
|
});
|
|
|
|
|
return $n;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2021-10-20 11:33:50 -04:00
|
|
|
|
sub restartBuilds {
|
2013-10-04 17:01:47 +02:00
|
|
|
|
my ($db, $builds) = @_;
|
|
|
|
|
|
2015-10-27 13:43:19 +01:00
|
|
|
|
$builds = $builds->search({ finished => 1 });
|
2013-10-04 17:01:47 +02:00
|
|
|
|
|
2015-10-27 13:43:19 +01:00
|
|
|
|
foreach my $build ($builds->search({}, { columns => ["drvpath"] })) {
|
|
|
|
|
next if !isValidPath($build->drvpath);
|
|
|
|
|
registerRoot $build->drvpath;
|
|
|
|
|
}
|
2014-07-18 00:02:59 +02:00
|
|
|
|
|
2015-10-27 13:43:19 +01:00
|
|
|
|
my $nrRestarted = 0;
|
2013-10-04 17:01:47 +02:00
|
|
|
|
|
2020-04-10 18:13:36 +02:00
|
|
|
|
$db->txn_do(sub {
|
2014-07-18 00:02:59 +02:00
|
|
|
|
# Reset the stats for the evals to which the builds belongs.
|
|
|
|
|
# !!! Should do this in a trigger.
|
2015-10-27 13:43:19 +01:00
|
|
|
|
$db->resultset('JobsetEvals')->search(
|
|
|
|
|
{ id => { -in => $builds->search({}, { join => { 'jobsetevalmembers' => 'eval' }, select => "jobsetevalmembers.eval", as => "eval", distinct => 1 })->as_query }
|
|
|
|
|
})->update({ nrsucceeded => undef });
|
2014-07-18 00:02:59 +02:00
|
|
|
|
|
2015-06-10 14:57:16 +02:00
|
|
|
|
# Clear the failed paths cache.
|
2013-10-04 17:01:47 +02:00
|
|
|
|
# FIXME: Add this to the API.
|
2015-10-27 13:43:19 +01:00
|
|
|
|
my $cleared = $db->resultset('FailedPaths')->search(
|
|
|
|
|
{ path => { -in => $builds->search({}, { join => "buildoutputs", select => "buildoutputs.path", as => "path", distinct => 1 })->as_query }
|
|
|
|
|
})->delete;
|
|
|
|
|
$cleared += $db->resultset('FailedPaths')->search(
|
|
|
|
|
{ path => { -in => $builds->search({}, { join => "buildstepoutputs", select => "buildstepoutputs.path", as => "path", distinct => 1 })->as_query }
|
|
|
|
|
})->delete;
|
|
|
|
|
print STDERR "cleared $cleared failed paths\n";
|
2015-10-28 14:54:54 +01:00
|
|
|
|
|
|
|
|
|
$nrRestarted = $builds->update(
|
|
|
|
|
{ finished => 0
|
|
|
|
|
, iscachedbuild => 0
|
|
|
|
|
});
|
2013-10-04 17:01:47 +02:00
|
|
|
|
});
|
|
|
|
|
|
2015-10-27 13:43:19 +01:00
|
|
|
|
return $nrRestarted;
|
2013-10-04 17:01:47 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2017-10-18 12:23:07 +02:00
|
|
|
|
sub getStoreUri {
|
|
|
|
|
my $config = getHydraConfig();
|
2017-10-18 15:33:55 +02:00
|
|
|
|
return $config->{'server_store_uri'} // $config->{'store_uri'} // "auto";
|
2017-10-18 12:23:07 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2017-04-01 10:38:39 -04:00
|
|
|
|
# Read a file from the (possibly remote) nix store
|
|
|
|
|
sub readNixFile {
|
|
|
|
|
my ($path) = @_;
|
2020-03-03 22:36:21 -05:00
|
|
|
|
return grab(cmd => ["nix", "--experimental-features", "nix-command",
|
|
|
|
|
"cat-store", "--store", getStoreUri(), "$path"]);
|
2017-10-18 12:23:07 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
sub isLocalStore {
|
|
|
|
|
my $uri = getStoreUri();
|
|
|
|
|
return $uri =~ "^(local|daemon|auto)";
|
2017-04-01 10:38:39 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2008-11-28 14:36:04 +00:00
|
|
|
|
1;
|