* Merged the Build and Job tables.

This commit is contained in:
Eelco Dolstra 2008-11-11 12:54:37 +00:00
parent 0f24c11292
commit ecd0ba74e9
20 changed files with 327 additions and 286 deletions

View file

@ -29,12 +29,17 @@ sub getBuild {
sub index :Path :Args(0) {
my ( $self, $c ) = @_;
$c->stash->{template} = 'index.tt';
$c->stash->{jobs} = [$c->model('DB::Jobs')->all];
$c->stash->{projects} = [$c->model('DB::Projects')->all];
$c->stash->{allBuilds} = [$c->model('DB::Builds')->search(undef, {order_by => "timestamp DESC"})];
# Get the latest build for each unique job.
# select * from builds as x where timestamp == (select max(timestamp) from builds where jobName == x.jobName);
$c->stash->{latestBuilds} = [$c->model('DB::Builds')->search(undef, {order_by => "project, attrName", where => "timestamp == (select max(timestamp) from builds where project == me.project and attrName == me.attrName)"})];
$c->stash->{scheduled} = [$c->model('DB::Builds')->search(
{finished => 0}, {join => 'schedulingInfo'})]; # !!!
$c->stash->{allBuilds} = [$c->model('DB::Builds')->search(
{finished => 1}, {order_by => "timestamp DESC"})];
# Get the latest finished build for each unique job.
$c->stash->{latestBuilds} = [$c->model('DB::Builds')->search(undef,
{ join => 'resultInfo'
, where => "finished != 0 and timestamp = (select max(timestamp) from Builds where project == me.project and attrName == me.attrName)"
, order_by => "project, attrname"
})];
}
@ -54,7 +59,9 @@ sub job :Local {
$c->stash->{template} = 'job.tt';
$c->stash->{projectName} = $project;
$c->stash->{jobName} = $jobName;
$c->stash->{builds} = [$c->model('DB::Builds')->search({project => $project, attrName => $jobName}, {order_by => "timestamp DESC"})];
$c->stash->{builds} = [$c->model('DB::Builds')->search(
{finished => 1, project => $project, attrName => $jobName},
{order_by => "timestamp DESC"})];
}

View file

@ -8,8 +8,8 @@ use base 'DBIx::Class::Schema';
__PACKAGE__->load_classes;
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-10 14:25:07
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:dBO/r6lVlITiJ/HlltKcpQ
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-11 13:41:38
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:1AgCf4sf5h2RU24Slo0sTA
# You can replace this text with custom content, and it will be preserved on regeneration

View file

@ -1,4 +1,4 @@
package HydraFrontend::Schema::Inputs;
package HydraFrontend::Schema::Buildinputs;
use strict;
use warnings;
@ -6,14 +6,12 @@ use warnings;
use base 'DBIx::Class';
__PACKAGE__->load_components("Core");
__PACKAGE__->table("inputs");
__PACKAGE__->table("BuildInputs");
__PACKAGE__->add_columns(
"id",
{ data_type => "integer", is_nullable => 0, size => undef },
"build",
{ data_type => "integer", is_nullable => 0, size => undef },
"job",
{ data_type => "integer", is_nullable => 0, size => undef },
"name",
{ data_type => "text", is_nullable => 0, size => undef },
"type",
@ -33,11 +31,16 @@ __PACKAGE__->add_columns(
);
__PACKAGE__->set_primary_key("id");
__PACKAGE__->belongs_to("build", "HydraFrontend::Schema::Builds", { id => "build" });
__PACKAGE__->belongs_to(
"dependency",
"HydraFrontend::Schema::Builds",
{ id => "dependency" },
);
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-10 14:25:07
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:A3Is4VTFkTl2DzrYjzdrZA
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-11 13:41:38
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:dKMSSomUN+gJX57Z5e295w
__PACKAGE__->belongs_to("dependency", "HydraFrontend::Schema::Builds", { id => "dependency" });
# You can replace this text with custom content, and it will be preserved on regeneration
1;

View file

@ -6,7 +6,7 @@ use warnings;
use base 'DBIx::Class';
__PACKAGE__->load_components("Core");
__PACKAGE__->table("buildLogs");
__PACKAGE__->table("BuildLogs");
__PACKAGE__->add_columns(
"build",
{ data_type => "integer", is_nullable => 0, size => undef },
@ -21,8 +21,8 @@ __PACKAGE__->set_primary_key("build", "logphase");
__PACKAGE__->belongs_to("build", "HydraFrontend::Schema::Builds", { id => "build" });
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-10 14:25:07
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:eMNna7u2l0ec+OYuvtGRpg
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-11 13:41:38
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:ZOxJeT+ltgyc/zuDl9aEDQ
# You can replace this text with custom content, and it will be preserved on regeneration

View file

@ -6,7 +6,7 @@ use warnings;
use base 'DBIx::Class';
__PACKAGE__->load_components("Core");
__PACKAGE__->table("buildProducts");
__PACKAGE__->table("BuildProducts");
__PACKAGE__->add_columns(
"build",
{ data_type => "integer", is_nullable => 0, size => undef },
@ -21,8 +21,8 @@ __PACKAGE__->set_primary_key("build", "path");
__PACKAGE__->belongs_to("build", "HydraFrontend::Schema::Builds", { id => "build" });
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-10 14:25:07
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:LaXQ4zxxvzdKFBRVcjMdMQ
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-11 13:41:38
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:rZPTilX/PAiIoxffxc0nJw
# You can replace this text with custom content, and it will be preserved on regeneration

View file

@ -0,0 +1,33 @@
package HydraFrontend::Schema::Buildresultinfo;
use strict;
use warnings;
use base 'DBIx::Class';
__PACKAGE__->load_components("Core");
__PACKAGE__->table("BuildResultInfo");
__PACKAGE__->add_columns(
"id",
{ data_type => "integer", is_nullable => 0, size => undef },
"iscachedbuild",
{ data_type => "integer", is_nullable => 0, size => undef },
"buildstatus",
{ data_type => "integer", is_nullable => 0, size => undef },
"errormsg",
{ data_type => "text", is_nullable => 0, size => undef },
"starttime",
{ data_type => "integer", is_nullable => 0, size => undef },
"stoptime",
{ data_type => "integer", is_nullable => 0, size => undef },
);
__PACKAGE__->set_primary_key("id");
__PACKAGE__->belongs_to("id", "HydraFrontend::Schema::Builds", { id => "id" });
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-11 13:41:38
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:2Vfqs9RUhbDrje18yZb3AA
# You can replace this text with custom content, and it will be preserved on regeneration
1;

View file

@ -6,10 +6,12 @@ use warnings;
use base 'DBIx::Class';
__PACKAGE__->load_components("Core");
__PACKAGE__->table("builds");
__PACKAGE__->table("Builds");
__PACKAGE__->add_columns(
"id",
{ data_type => "integer", is_nullable => 0, size => undef },
"finished",
{ data_type => "integer", is_nullable => 0, size => undef },
"timestamp",
{ data_type => "integer", is_nullable => 0, size => undef },
"project",
@ -24,16 +26,6 @@ __PACKAGE__->add_columns(
{ data_type => "text", is_nullable => 0, size => undef },
"outpath",
{ data_type => "text", is_nullable => 0, size => undef },
"iscachedbuild",
{ data_type => "integer", is_nullable => 0, size => undef },
"buildstatus",
{ data_type => "integer", is_nullable => 0, size => undef },
"errormsg",
{ data_type => "text", is_nullable => 0, size => undef },
"starttime",
{ data_type => "integer", is_nullable => 0, size => undef },
"stoptime",
{ data_type => "integer", is_nullable => 0, size => undef },
"system",
{ data_type => "text", is_nullable => 0, size => undef },
);
@ -49,10 +41,25 @@ __PACKAGE__->belongs_to(
{ name => "jobset", project => "project" },
);
__PACKAGE__->has_many(
"inputs",
"HydraFrontend::Schema::Inputs",
"buildschedulinginfoes",
"HydraFrontend::Schema::Buildschedulinginfo",
{ "foreign.id" => "self.id" },
);
__PACKAGE__->has_many(
"buildresultinfoes",
"HydraFrontend::Schema::Buildresultinfo",
{ "foreign.id" => "self.id" },
);
__PACKAGE__->has_many(
"buildinputs_builds",
"HydraFrontend::Schema::Buildinputs",
{ "foreign.build" => "self.id" },
);
__PACKAGE__->has_many(
"buildinputs_dependencies",
"HydraFrontend::Schema::Buildinputs",
{ "foreign.dependency" => "self.id" },
);
__PACKAGE__->has_many(
"buildproducts",
"HydraFrontend::Schema::Buildproducts",
@ -65,9 +72,23 @@ __PACKAGE__->has_many(
);
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-10 14:25:07
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:C1XPkCXQImyXduKER0Dllg
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-11 13:41:38
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:1GZeB3YVr064AZrGargmFg
__PACKAGE__->has_many(dependents => 'HydraFrontend::Schema::Inputs', 'dependency');
__PACKAGE__->has_many(dependents => 'HydraFrontend::Schema::Buildinputs', 'dependency');
__PACKAGE__->has_many(inputs => 'HydraFrontend::Schema::Buildinputs', 'build');
__PACKAGE__->belongs_to(
"schedulingInfo",
"HydraFrontend::Schema::Buildschedulinginfo",
{ id => "id" },
);
__PACKAGE__->belongs_to(
"resultInfo",
"HydraFrontend::Schema::Buildresultinfo",
{ id => "id" },
);
1;

View file

@ -0,0 +1,31 @@
package HydraFrontend::Schema::Buildschedulinginfo;
use strict;
use warnings;
use base 'DBIx::Class';
__PACKAGE__->load_components("Core");
__PACKAGE__->table("BuildSchedulingInfo");
__PACKAGE__->add_columns(
"id",
{ data_type => "integer", is_nullable => 0, size => undef },
"priority",
{ data_type => "integer", is_nullable => 0, size => undef },
"busy",
{ data_type => "integer", is_nullable => 0, size => undef },
"locker",
{ data_type => "text", is_nullable => 0, size => undef },
"logfile",
{ data_type => "text", is_nullable => 0, size => undef },
);
__PACKAGE__->set_primary_key("id");
__PACKAGE__->belongs_to("id", "HydraFrontend::Schema::Builds", { id => "id" });
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-11 13:41:38
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:rN7v2+MnC8TkrEHUzt2Gqg
# You can replace this text with custom content, and it will be preserved on regeneration
1;

View file

@ -1,58 +0,0 @@
package HydraFrontend::Schema::Jobs;
use strict;
use warnings;
use base 'DBIx::Class';
__PACKAGE__->load_components("Core");
__PACKAGE__->table("jobs");
__PACKAGE__->add_columns(
"id",
{ data_type => "integer", is_nullable => 0, size => undef },
"timestamp",
{ data_type => "integer", is_nullable => 0, size => undef },
"priority",
{ data_type => "integer", is_nullable => 0, size => undef },
"busy",
{ data_type => "integer", is_nullable => 0, size => undef },
"locker",
{ data_type => "text", is_nullable => 0, size => undef },
"project",
{ data_type => "text", is_nullable => 0, size => undef },
"jobset",
{ data_type => "text", is_nullable => 0, size => undef },
"attrname",
{ data_type => "text", is_nullable => 0, size => undef },
"description",
{ data_type => "text", is_nullable => 0, size => undef },
"drvpath",
{ data_type => "text", is_nullable => 0, size => undef },
"outpath",
{ data_type => "text", is_nullable => 0, size => undef },
"system",
{ data_type => "text", is_nullable => 0, size => undef },
);
__PACKAGE__->set_primary_key("id");
__PACKAGE__->belongs_to(
"project",
"HydraFrontend::Schema::Projects",
{ name => "project" },
);
__PACKAGE__->belongs_to(
"jobset",
"HydraFrontend::Schema::Jobsets",
{ name => "jobset", project => "project" },
);
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-10 14:25:07
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:ZF8UB1MtbPuOk7wTSFJR5Q
__PACKAGE__->has_many(
"inputs",
"HydraFrontend::Schema::Inputs",
{ "foreign.job" => "self.id" },
);
1;

View file

@ -6,7 +6,7 @@ use warnings;
use base 'DBIx::Class';
__PACKAGE__->load_components("Core");
__PACKAGE__->table("jobsetInputAlts");
__PACKAGE__->table("JobsetInputAlts");
__PACKAGE__->add_columns(
"project",
{ data_type => "text", is_nullable => 0, size => undef },
@ -33,8 +33,8 @@ __PACKAGE__->belongs_to(
);
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-10 14:25:07
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:ibTncC1AslPWt1eiTtwplA
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-11 13:41:38
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:bvEulSFMDlAMs39sIyHgZQ
# You can replace this text with custom content, and it will be preserved on regeneration

View file

@ -6,7 +6,7 @@ use warnings;
use base 'DBIx::Class';
__PACKAGE__->load_components("Core");
__PACKAGE__->table("jobsetInputs");
__PACKAGE__->table("JobsetInputs");
__PACKAGE__->add_columns(
"project",
{ data_type => "text", is_nullable => 0, size => undef },
@ -43,8 +43,8 @@ __PACKAGE__->has_many(
);
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-10 14:25:07
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:D1UzSZwPtwDmOI7q6g8uKQ
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-11 13:41:38
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:54xK3D1D0Jm5oKgRelXN7Q
# You can replace this text with custom content, and it will be preserved on regeneration

View file

@ -6,7 +6,7 @@ use warnings;
use base 'DBIx::Class';
__PACKAGE__->load_components("Core");
__PACKAGE__->table("jobsets");
__PACKAGE__->table("Jobsets");
__PACKAGE__->add_columns(
"name",
{ data_type => "text", is_nullable => 0, size => undef },
@ -46,18 +46,10 @@ __PACKAGE__->has_many(
"foreign.project" => "self.project",
},
);
__PACKAGE__->has_many(
"jobs",
"HydraFrontend::Schema::Jobs",
{
"foreign.jobset" => "self.name",
"foreign.project" => "self.project",
},
);
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-10 14:25:07
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:6Pyrgervmq03S5Nx8QfA1Q
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-11 13:41:38
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:JHirlq7Jc8dQOy+Op/VflA
# You can replace this text with custom content, and it will be preserved on regeneration

View file

@ -6,7 +6,7 @@ use warnings;
use base 'DBIx::Class';
__PACKAGE__->load_components("Core");
__PACKAGE__->table("projects");
__PACKAGE__->table("Projects");
__PACKAGE__->add_columns(
"name",
{ data_type => "text", is_nullable => 0, size => undef },
@ -22,15 +22,10 @@ __PACKAGE__->has_many(
"HydraFrontend::Schema::Jobsets",
{ "foreign.project" => "self.name" },
);
__PACKAGE__->has_many(
"jobs",
"HydraFrontend::Schema::Jobs",
{ "foreign.project" => "self.name" },
);
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-10 14:25:07
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:p8LbF31qRl/JfMK5wfkeCg
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-11 13:41:38
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:7Ag5ZfYVgfw3MJZkNUmBYw
# You can replace this text with custom content, and it will be preserved on regeneration

View file

@ -1,7 +1,12 @@
[% WRAPPER layout.tt title="Hydra Overview" %]
[% USE date %]
<h1>Job <tt>[% build.project.name %]:[% build.attrname %]</tt> build [% id %]</h1>
<h1>
Job <tt>[% build.project.name %]:[% build.attrname %]</tt> build [% id %]
[% IF !build.finished %]
(scheduled)
[% END %]
</h1>
<h2>Information</h2>
@ -11,6 +16,10 @@
<th>Build ID:</th>
<td>[% build.id %]</td>
</tr>
<tr>
<th>Time added:</th>
<td>[% date.format(build.timestamp, '%Y-%m-%d %H:%M:%S') %]</td>
</tr>
<tr>
<th>Project:</th>
<td><a href="[% c.uri_for('/project' build.project.name) %]"><tt>[% build.project.name %]</tt></a></td>
@ -28,26 +37,8 @@
<td>[% build.description %]</td>
</tr>
<tr>
<th>Time added:</th>
<td>[% date.format(build.timestamp, '%Y-%m-%d %H:%M:%S') %]</td>
</tr>
<tr>
<th>Build started:</th>
<td>[% IF build.starttime %][% date.format(build.starttime, '%Y-%m-%d %H:%M:%S') %][% ELSE %]<em>(cached build)</em>[% END %]</td>
</tr>
<tr>
<th>Build finished:</th>
<td>[% IF build.stoptime %][% date.format(build.stoptime, '%Y-%m-%d %H:%M:%S') %][% ELSE %]<em>(cached build)</em>[% END %]</td>
</tr>
<tr>
<th>Duration (seconds):</th>
<td>
[% IF build.iscachedbuild %]
<em>(cached build)</em>
[% ELSE %]
[% build.stoptime - build.starttime %]
[% END %]
</td>
<th>System:</th>
<td><tt>[% build.system %]</tt></td>
</tr>
<tr>
<th>Derivation store path:</th>
@ -57,20 +48,41 @@
<th>Output store path:</th>
<td><tt>[% build.outpath %]</tt></td>
</tr>
[% IF build.finished %]
<tr>
<th>System:</th>
<td><tt>[% build.system %]</tt></td>
<th>Build started:</th>
<td>[% IF build.resultInfo.starttime %][% date.format(build.resultInfo.starttime, '%Y-%m-%d %H:%M:%S') %][% ELSE %]<em>(cached build)</em>[% END %]</td>
</tr>
<tr>
<th>Build finished:</th>
<td>[% IF build.resultInfo.stoptime %][% date.format(build.resultInfo.stoptime, '%Y-%m-%d %H:%M:%S') %][% ELSE %]<em>(cached build)</em>[% END %]</td>
</tr>
<tr>
<th>Duration (seconds):</th>
<td>
[% IF build.resultInfo.iscachedbuild %]
<em>(cached build)</em>
[% ELSE %]
[% build.resultInfo.stoptime - build.resultInfo.starttime %]
[% END %]
</td>
</tr>
<tr>
<th>Status:</th>
<td>
[% IF build.buildstatus == 0 %]
[% IF build.resultInfo.buildstatus == 0 %]
<img src="/static/images/success.gif" />
[% ELSE %]
<img src="/static/images/failure.gif" />
[% END %]
</td>
</tr>
[% ELSE %]
<tr>
<th>Priority:</th>
<td>[% build.schedulingInfo.priority %]</td>
</tr>
[% END %]
</table>
@ -101,6 +113,8 @@
</table>
[% IF build.finished %]
<h2>Build products</h2>
<ul class="productList">
@ -167,3 +181,6 @@
[% END %]
[% END %]

View file

@ -8,15 +8,15 @@
<tr><th>#</th><th>Priority</th><th>Project</th><th>Job</th><th>System</th><th>Timestamp</th><th>Description</th></tr>
</thead>
<tbody>
[% FOREACH job IN jobs -%]
<tr [% IF job.busy %]class="runningJob"[% END %] >
<td>[% job.id %]</td>
<td>[% job.priority %]</td>
<td><tt>[% job.project.name %]</tt></td>
<td><tt>[% job.jobset.name %]</tt></td>
<td><tt>[% job.system %]</tt></td>
<td>[% date.format(job.timestamp, '%Y-%m-%d %H:%M:%S') %]</td>
<td>[% job.description %]</td>
[% FOREACH build IN scheduled -%]
<tr [% IF build.schedulingInfo.busy %]class="runningJob"[% END %] >
<td><a href="[% c.uri_for('/build' build.id) %]">[% build.id %]</a></td>
<td>[% build.schedulingInfo.priority %]</td>
<td><tt>[% build.project.name %]</tt></td>
<td><tt>[% build.jobset.name %]</tt></td>
<td><tt>[% build.system %]</tt></td>
<td>[% date.format(build.timestamp, '%Y-%m-%d %H:%M:%S') %]</td>
<td>[% build.description %]</td>
</tr>
[% END -%]
</tbody>

View file

@ -2,7 +2,7 @@
<tr>
<td>
[% IF build.buildstatus == 0 %]
[% IF build.resultInfo.buildstatus == 0 %]
<img src="/static/images/success.gif" />
[% ELSE %]
<img src="/static/images/failure.gif" />

View file

@ -16,11 +16,11 @@ sub isValidPath {
}
sub buildJob {
my ($job) = @_;
sub doBuild {
my ($build) = @_;
my $drvPath = $job->drvpath;
my $outPath = $job->outpath;
my $drvPath = $build->drvpath;
my $outPath = $build->outpath;
my $isCachedBuild = 1;
my $outputCreated = 1; # i.e., the Nix build succeeded (but it could be a positive failure)
@ -52,27 +52,17 @@ sub buildJob {
}
$db->txn_do(sub {
my $build = $db->resultset('Builds')->create(
{ timestamp => time()
, project => $job->project->name
, jobset => $job->jobset->name
, attrname => $job->attrname
, description => $job->description
, drvpath => $drvPath
, outpath => $outPath
$build->finished(1);
$build->timestamp(time());
$build->update;
$db->resultset('Buildresultinfo')->create(
{ id => $build->id
, iscachedbuild => $isCachedBuild
, buildstatus => $buildStatus
, starttime => $startTime
, stoptime => $stopTime
, system => $job->system
});
print " build ID = ", $build->id, "\n";
foreach my $input ($job->inputs) {
$input->job(undef);
$input->build($build->id);
$input->update;
}
my $logPath = "/nix/var/log/nix/drvs/" . basename $drvPath;
if (-e $logPath) {
@ -125,47 +115,44 @@ sub buildJob {
}
}
$job->delete;
$build->schedulingInfo->delete;
});
print "STOP ", time, "\n";
}
my $jobId = $ARGV[0] or die;
print "building job $jobId\n";
my $buildId = $ARGV[0] or die;
print "performing build $buildId\n";
# Lock the job. If necessary, steal the lock from the parent process
# (runner.pl). This is so that if the runner dies, the children
# (i.e. the job builders) can continue to run and won't have the lock
# taken away.
my $job;
# Lock the build. If necessary, steal the lock from the parent
# process (runner.pl). This is so that if the runner dies, the
# children (i.e. the build.pl instances) can continue to run and won't
# have the lock taken away.
my $build;
$db->txn_do(sub {
($job) = $db->resultset('Jobs')->search({ id => $jobId });
die "job $jobId doesn't exist" unless defined $job;
if ($job->busy != 0 && $job->locker != getppid) {
die "job $jobId is already being built";
($build) = $db->resultset('Builds')->search({id => $buildId});
die "build $buildId doesn't exist" unless defined $build;
if ($build->schedulingInfo->busy != 0 && $build->schedulingInfo->locker != getppid) {
die "build $buildId is already being built";
}
$job->busy(1);
$job->locker($$);
$job->update;
$build->schedulingInfo->busy(1);
$build->schedulingInfo->locker($$);
$build->schedulingInfo->update;
});
die unless $job;
die unless $build;
# Build the job. If it throws an error, unlock the job so that it can
# be retried.
# Do the build. If it throws an error, unlock the build so that it
# can be retried.
eval {
print "BUILD\n";
buildJob $job;
doBuild $build;
print "DONE\n";
};
if ($@) {
warn $@;
$db->txn_do(sub {
$job->busy(0);
$job->locker($$);
$job->update;
$build->schedulingInfo->busy(0);
$build->schedulingInfo->locker($$);
$build->schedulingInfo->update;
});
}

View file

@ -1,6 +1,15 @@
create table builds (
-- This table contains all builds, either scheduled or finished. For
-- scheduled builds, additional info (such as the priority) can be
-- found in the BuildSchedulingInfo table. For finished builds,
-- additional info (such as the logs, build products, etc.) can be
-- found in several tables, such as BuildResultInfo, BuildLogs and
-- BuildProducts.
create table Builds (
id integer primary key autoincrement not null,
timestamp integer not null, -- time this build was added to the db (in Unix time)
finished integer not null, -- 0 = scheduled, 1 = finished
timestamp integer not null, -- time this build was scheduled / finished building
-- Info about the inputs.
project text not null, -- !!! foreign key
@ -11,25 +20,51 @@ create table builds (
description text,
drvPath text not null,
outPath text not null,
isCachedBuild integer not null, -- boolean
buildStatus integer, -- 0 = succeeded, 1 = Nix build failure, 2 = positive build failure
errorMsg text, -- error message in case of a Nix failure
startTime integer, -- in Unix time, 0 = used cached build result
stopTime integer,
system text not null,
foreign key (project) references projects(name), -- ignored by sqlite
foreign key (project, jobset) references jobsets(project, name) -- ignored by sqlite
foreign key (project) references Projects(name), -- ignored by sqlite
foreign key (project, jobset) references Jobsets(project, name) -- ignored by sqlite
);
-- Inputs of jobs/builds.
create table inputs (
-- Info for a scheduled build.
create table BuildSchedulingInfo (
id integer primary key not null,
priority integer not null default 0,
busy integer not null default 0, -- true means someone is building this job now
locker text not null default '', -- !!! hostname/pid of the process building this job?
logfile text, -- if busy, the path of the logfile
foreign key (id) references Builds(id) on delete cascade -- ignored by sqlite
);
-- Info for a finished build.
create table BuildResultInfo (
id integer primary key not null,
isCachedBuild integer not null, -- boolean
buildStatus integer, -- 0 = succeeded, 1 = Nix build failure, 2 = positive build failure
errorMsg text, -- error message in case of a Nix failure
startTime integer, -- in Unix time, 0 = used cached build result
stopTime integer,
foreign key (id) references Builds(id) on delete cascade -- ignored by sqlite
);
-- Inputs of builds.
create table BuildInputs (
id integer primary key autoincrement not null,
-- Which job or build this input belongs to. Exactly one must be non-null.
-- Which build this input belongs to.
build integer,
job integer,
-- Copied from the jobsetinputs from which the build was created.
name text not null,
@ -42,29 +77,28 @@ create table inputs (
path text,
foreign key (build) references builds(id) -- ignored by sqlite
foreign key (job) references jobs(id) -- ignored by sqlite
foreign key (dependency) references builds(id) -- ignored by sqlite
foreign key (build) references Builds(id) on delete cascade, -- ignored by sqlite
foreign key (dependency) references Builds(id) -- ignored by sqlite
);
create table buildProducts (
create table BuildProducts (
build integer not null,
path text not null,
type text not null, -- "nix-build", "file", "doc", "report", ...
subtype text not null, -- "source-dist", "rpm", ...
primary key (build, path),
foreign key (build) references builds(id) on delete cascade -- ignored by sqlite
foreign key (build) references Builds(id) on delete cascade -- ignored by sqlite
);
create table buildLogs (
create table BuildLogs (
build integer not null,
logPhase text not null,
path text not null,
type text not null,
primary key (build, logPhase),
foreign key (build) references builds(id) on delete cascade -- ignored by sqlite
foreign key (build) references Builds(id) on delete cascade -- ignored by sqlite
);
@ -72,13 +106,15 @@ create table buildLogs (
create trigger cascadeBuildDeletion
before delete on builds
for each row begin
--delete from buildInputs where build = old.id;
delete from buildLogs where build = old.id;
delete from buildProducts where build = old.id;
delete from BuildSchedulingInfo where id = old.id;
delete from BuildResultInfo where id = old.id;
delete from BuildInputs where build = old.id;
delete from BuildLogs where build = old.id;
delete from BuildProducts where build = old.id;
end;
create table projects (
create table Projects (
name text primary key not null
);
@ -86,29 +122,29 @@ create table projects (
-- A jobset consists of a set of inputs (e.g. SVN repositories), one
-- of which contains a Nix expression containing an attribute set
-- describing build jobs.
create table jobsets (
create table Jobsets (
name text not null,
project text not null,
description text,
nixExprInput text not null, -- name of the jobsetInput containing the Nix expression
nixExprPath text not null, -- relative path of the Nix expression
primary key (project, name),
foreign key (project) references projects(name) on delete cascade, -- ignored by sqlite
foreign key (project, name, nixExprInput) references jobsetInputs(project, job, name)
foreign key (project) references Projects(name) on delete cascade, -- ignored by sqlite
foreign key (project, name, nixExprInput) references JobsetInputs(project, job, name)
);
create table jobsetInputs (
create table JobsetInputs (
project text not null,
jobset text not null,
name text not null,
type text not null, -- "svn", "cvs", "path", "file", "string"
primary key (project, jobset, name),
foreign key (project, jobset) references jobsets(project, name) on delete cascade -- ignored by sqlite
foreign key (project, jobset) references Jobsets(project, name) on delete cascade -- ignored by sqlite
);
create table jobsetInputAlts (
create table JobsetInputAlts (
project text not null,
jobset text not null,
input text not null,
@ -121,30 +157,5 @@ create table jobsetInputAlts (
value text, -- for type == 'string'
primary key (project, jobset, input, altnr),
foreign key (project, jobset, input) references jobsetInputs(project, jobset, name) on delete cascade -- ignored by sqlite
);
create table jobs (
id integer primary key autoincrement not null,
timestamp integer not null, -- time this build was added to the db (in Unix time)
priority integer not null default 0,
busy integer not null default 0, -- true means someone is building this job now
locker text not null default '', -- !!! hostname/pid of the process building this job?
-- Info about the inputs.
project text not null, -- !!! foreign key
jobset text not null, -- !!! foreign key
attrName text not null,
-- What this job will build.
description text,
drvPath text not null,
outPath text not null,
system text not null,
foreign key (project) references projects(name), -- ignored by sqlite
foreign key (project, jobset) references jobsets(project, name) -- ignored by sqlite
foreign key (project, jobset, input) references JobsetInputs(project, jobset, name) on delete cascade -- ignored by sqlite
);

View file

@ -12,14 +12,16 @@ $db->storage->dbh->do("PRAGMA synchronous = OFF;");
# Unlock jobs whose building process has died.
$db->txn_do(sub {
my @jobs = $db->resultset('Jobs')->search({ busy => 1 });
my @jobs = $db->resultset('Builds')->search(
{finished => 0, busy => 1}, {join => 'schedulingInfo'});
foreach my $job (@jobs) {
my $pid = $job->locker;
print $job, "\n";
my $pid = $job->schedulingInfo->locker;
if (kill(0, $pid) != 1) { # see if we can signal the process
print "job ", $job->id, " pid $pid died, unlocking\n";
$job->busy(0);
$job->locker("");
$job->update;
$job->schedulingInfo->busy(0);
$job->schedulingInfo->locker("");
$job->schedulingInfo->update;
}
}
});
@ -32,15 +34,17 @@ sub checkJobs {
$db->txn_do(sub {
my @jobs = $db->resultset('Jobs')->search({ busy => 0 }, {order_by => ["priority", "timestamp"]});
my @jobs = $db->resultset('Builds')->search(
{finished => 0, busy => 0},
{join => 'schedulingInfo', order_by => ["priority", "timestamp"]});
print "# of available jobs: ", scalar(@jobs), "\n";
if (scalar @jobs > 0) {
$job = $jobs[0];
$job->busy(1);
$job->locker($$);
$job->update;
$job->schedulingInfo->busy(1);
$job->schedulingInfo->locker($$);
$job->schedulingInfo->update;
}
});
@ -66,9 +70,9 @@ sub checkJobs {
if ($@) {
warn $@;
$db->txn_do(sub {
$job->busy(0);
$job->locker($$);
$job->update;
$job->schedulingInfo->busy(0);
$job->schedulingInfo->locker($$);
$job->schedulingInfo->update;
});
}
}

View file

@ -67,24 +67,15 @@ sub checkJob {
{ project => $project->name, jobset => $jobset->name
, attrname => $jobName, outPath => $outPath })) > 0)
{
print " already done\n";
return;
}
if (scalar($db->resultset('Jobs')->search(
{ project => $project->name, jobset => $jobset->name
, attrname => $jobName, outPath => $outPath })) > 0)
{
print " already queued\n";
print " already scheduled/done\n";
return;
}
print " adding to queue\n";
my $job = $db->resultset('Jobs')->create(
{ timestamp => time()
, priority => 0
, busy => 0
, locker => ""
my $build = $db->resultset('Builds')->create(
{ finished => 0
, timestamp => time()
, project => $project->name
, jobset => $jobset->name
, attrname => $jobName
@ -94,10 +85,17 @@ sub checkJob {
, system => $job->{system}
});
$db->resultset('Buildschedulinginfo')->create(
{ id => $build->id
, priority => 0
, busy => 0
, locker => ""
});
foreach my $inputName (keys %{$inputInfo}) {
my $input = $inputInfo->{$inputName};
$db->resultset('Inputs')->create(
{ job => $job->id
$db->resultset('Buildinputs')->create(
{ build => $build->id
, name => $inputName
, type => $input->{type}
, uri => $input->{uri}
@ -151,8 +149,8 @@ sub checkJobAlternatives {
else {
(my $prevBuild) = $db->resultset('Builds')->search(
{project => $project->name, jobset => $jobset->name, attrname => $argName, buildStatus => 0},
{order_by => "timestamp DESC", rows => 1});
{finished => 1, project => $project->name, jobset => $jobset->name, attrname => $argName, buildStatus => 0},
{join => 'resultInfo', order_by => "timestamp DESC", rows => 1});
if (!defined $prevBuild) {
# !!! reschedule?