* Basic release management: releases are now dynamically computed as

database queries from a set of jobs that have to be in a release.
  E.g. a patchelf release might consist of the jobs "tarball", "build",
  and "rpm_fedora10i386".  Here the first job ("tarball") is the
  primary job: all the others have it as an input.  The primary job
  supplies the identity of the release.
This commit is contained in:
Eelco Dolstra 2008-11-27 15:16:06 +00:00
parent 24923ed09a
commit 98c53156e6
27 changed files with 347 additions and 39 deletions

View file

@ -41,7 +41,7 @@ sub trim {
sub getBuild {
my ($c, $id) = @_;
(my $build) = $c->model('DB::Builds')->search({ id => $id });
my $build = $c->model('DB::Builds')->find($id);
return $build;
}
@ -168,6 +168,84 @@ sub all :Local {
}
sub releasesets :Local {
my ($self, $c, $projectName) = @_;
$c->stash->{template} = 'releasesets.tt';
my $project = $c->model('DB::Projects')->find($projectName);
return error($c, "Project $projectName doesn't exist.") if !defined $project;
$c->stash->{curProject} = $project;
$c->stash->{releaseSets} = [$project->releasesets->all];
}
sub releases :Local {
my ($self, $c, $projectName, $releaseName) = @_;
$c->stash->{template} = 'releases.tt';
my $project = $c->model('DB::Projects')->find($projectName);
return error($c, "Project $projectName doesn't exist.") if !defined $project;
$c->stash->{curProject} = $project;
(my $releaseSet) = $c->model('DB::Releasesets')->find($projectName, $releaseName);
return error($c, "Release set $releaseName doesn't exist.") if !defined $releaseSet;
$c->stash->{releaseSet} = $releaseSet;
(my $primaryJob) = $releaseSet->releasesetjobs->search({isprimary => 1});
return error($c, "Release set $releaseName doesn't have a primary job.") if !defined $primaryJob;
$c->stash->{jobs} = [$releaseSet->releasesetjobs->search({}, {order_by => "isprimary DESC"})];
my @primaryBuilds = $project->builds->search(
{ attrname => $primaryJob->job, finished => 1 },
{ join => 'resultInfo', order_by => "timestamp DESC", '+select' => ["resultInfo.releasename"], '+as' => ["releasename"] });
my @releases = ();
foreach my $primaryBuild (@primaryBuilds) {
my @jobs = ();
my $status = 0; # = okay
foreach my $job (@{$c->stash->{jobs}}) {
my $thisBuild;
if ($job->isprimary == 1) {
$thisBuild = $primaryBuild;
} else {
# Find a build of this job that had the primary build
# as input. If there are multiple, prefer successful
# ones, and then oldest. !!! order_by buildstatus is hacky
($thisBuild) = $primaryBuild->dependentBuilds->search(
{ attrname => $job->job, finished => 1 },
{ join => 'resultInfo', rows => 1
, order_by => ["buildstatus", "timestamp"] });
}
if ($job->mayfail != 1) {
if (!defined $thisBuild) {
$status = 2 if $status == 0; # = unfinished
} elsif ($thisBuild->resultInfo->buildstatus != 0) {
$status = 1; # = failed
}
}
push @jobs, { build => $thisBuild };
}
push @releases,
{ id => $primaryBuild->id
, releasename => $primaryBuild->get_column('releasename')
, jobs => [@jobs]
, status => $status
};
}
$c->stash->{releases} = [@releases];
}
sub updateProject {
my ($c, $project) = @_;
my $projectName = trim $c->request->params->{name};
@ -296,7 +374,7 @@ sub project :Local {
my ($self, $c, $projectName, $subcommand, $arg) = @_;
$c->stash->{template} = 'project.tt';
(my $project) = $c->model('DB::Projects')->search({ name => $projectName });
my $project = $c->model('DB::Projects')->find($projectName);
return error($c, "Project $projectName doesn't exist.") if !defined $project;
my $isPosted = $c->request->method eq "POST";
@ -386,7 +464,7 @@ sub job :Local {
my ($self, $c, $projectName, $jobName) = @_;
$c->stash->{template} = 'job.tt';
(my $project) = $c->model('DB::Projects')->search({ name => $projectName });
my $project = $c->model('DB::Projects')->find($projectName);
return error($c, "Project $projectName doesn't exist.") if !defined $project;
$c->stash->{curProject} = $project;

View file

@ -8,8 +8,8 @@ use base 'DBIx::Class::Schema';
__PACKAGE__->load_classes;
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-27 03:26:23
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:yXQEjv8/1aoKNW095xSR/Q
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-27 14:48:09
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:jJnmW70e1RDsSt5ClahomQ
# You can replace this text with custom content, and it will be preserved on regeneration

View file

@ -36,8 +36,8 @@ __PACKAGE__->belongs_to("build", "Hydra::Schema::Builds", { id => "build" });
__PACKAGE__->belongs_to("dependency", "Hydra::Schema::Builds", { id => "dependency" });
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-27 03:26:23
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:uaNcxZMTbF9WDLgf2G1Klw
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-27 14:48:09
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:R1F2JbVygktvK55xmY8mcg
# You can replace this text with custom content, and it will be preserved on regeneration

View file

@ -33,8 +33,8 @@ __PACKAGE__->set_primary_key("build", "productnr");
__PACKAGE__->belongs_to("build", "Hydra::Schema::Builds", { id => "build" });
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-27 03:26:23
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:btk6BJGE0Hj9qTO4qChpfw
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-27 14:48:09
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:aZuZd+oUAO1c8GvSbgn7Fw
# You can replace this text with custom content, and it will be preserved on regeneration

View file

@ -29,8 +29,8 @@ __PACKAGE__->set_primary_key("id");
__PACKAGE__->belongs_to("id", "Hydra::Schema::Builds", { id => "id" });
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-27 03:26:23
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:Cn7vCpqfbTiq1/JF48BG2Q
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-27 14:48:09
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:QahlwGdZKC7mL7fvwNxWjA
# You can replace this text with custom content, and it will be preserved on regeneration

View file

@ -70,11 +70,13 @@ __PACKAGE__->has_many(
);
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-27 03:26:23
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:p67v2RE44sAk2yGFoTpPww
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-27 14:48:09
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:uRSa4YkaRG0K6vK/qhGI9w
__PACKAGE__->has_many(dependents => 'Hydra::Schema::Buildinputs', 'dependency');
__PACKAGE__->many_to_many(dependentBuilds => 'dependents', 'build');
__PACKAGE__->has_many(inputs => 'Hydra::Schema::Buildinputs', 'build');
__PACKAGE__->belongs_to(

View file

@ -25,8 +25,8 @@ __PACKAGE__->set_primary_key("id");
__PACKAGE__->belongs_to("id", "Hydra::Schema::Builds", { id => "id" });
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-27 03:26:23
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:hdFMzqZ1IIdypz+/KLoCIw
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-27 14:48:09
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:xBocoeipFdRsWDhvtoXImA
# You can replace this text with custom content, and it will be preserved on regeneration

View file

@ -35,8 +35,8 @@ __PACKAGE__->set_primary_key("id", "stepnr");
__PACKAGE__->belongs_to("id", "Hydra::Schema::Builds", { id => "id" });
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-27 03:26:23
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:zFljaYEbDkYbHuCmcIJhOA
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-27 14:48:09
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:04BankpQ6xo6T/ioMTdWkQ
# You can replace this text with custom content, and it will be preserved on regeneration

View file

@ -22,8 +22,8 @@ __PACKAGE__->add_columns(
__PACKAGE__->set_primary_key("srcpath", "sha256hash");
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-27 03:26:23
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:E9++anIBM/+OIi2UdhIZKA
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-27 14:48:09
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:Nq3TpcRmpSRWNL4Q1hGGrA
# You can replace this text with custom content, and it will be preserved on regeneration

View file

@ -20,8 +20,8 @@ __PACKAGE__->add_columns(
__PACKAGE__->set_primary_key("uri", "revision");
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-27 03:26:23
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:eKcfAgBW789dI2VFGh4baw
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-27 14:48:09
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:CCbHomM+8BTBqHBeGOGcuA
# You can replace this text with custom content, and it will be preserved on regeneration

View file

@ -33,8 +33,8 @@ __PACKAGE__->belongs_to(
);
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-27 03:26:23
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:vEw8HtMT848S/GEL1Y1MUg
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-27 14:48:09
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:JPf4ozBKK6NQPJT2few40g
# You can replace this text with custom content, and it will be preserved on regeneration

View file

@ -43,8 +43,8 @@ __PACKAGE__->has_many(
);
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-27 03:26:23
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:JVmtu+NXI6P/GD5q7+YTDA
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-27 14:48:09
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:S8z1W0kjUX9VN5HPjyGAzA
# You can replace this text with custom content, and it will be preserved on regeneration

View file

@ -50,8 +50,8 @@ __PACKAGE__->has_many(
);
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-27 03:26:23
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:e1BZx0WYj1b6iIov6KvCqA
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-27 14:48:09
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:ebblUCTW7I1wGhVlPfNd3Q
# You can replace this text with custom content, and it will be preserved on regeneration

View file

@ -30,10 +30,20 @@ __PACKAGE__->has_many(
"Hydra::Schema::Jobsets",
{ "foreign.project" => "self.name" },
);
__PACKAGE__->has_many(
"releasesets",
"Hydra::Schema::Releasesets",
{ "foreign.project" => "self.name" },
);
__PACKAGE__->has_many(
"releasesetjobs",
"Hydra::Schema::Releasesetjobs",
{ "foreign.project" => "self.name" },
);
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-27 03:26:23
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:BHYbrizctvmbAJyTKSu89g
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-27 14:48:09
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:70/Br6966ZZ+p8n6lF1hcw
# You can replace this text with custom content, and it will be preserved on regeneration

View file

@ -0,0 +1,40 @@
package Hydra::Schema::Releasesetjobs;
use strict;
use warnings;
use base 'DBIx::Class';
__PACKAGE__->load_components("Core");
__PACKAGE__->table("ReleaseSetJobs");
__PACKAGE__->add_columns(
"project",
{ data_type => "text", is_nullable => 0, size => undef },
"release",
{ data_type => "text", is_nullable => 0, size => undef },
"job",
{ data_type => "text", is_nullable => 0, size => undef },
"attrs",
{ data_type => "text", is_nullable => 0, size => undef },
"isprimary",
{ data_type => "integer", is_nullable => 0, size => undef },
"mayfail",
{ data_type => "integer", is_nullable => 0, size => undef },
"description",
{ data_type => "text", is_nullable => 0, size => undef },
);
__PACKAGE__->set_primary_key("project", "release", "job", "attrs");
__PACKAGE__->belongs_to("project", "Hydra::Schema::Projects", { name => "project" });
__PACKAGE__->belongs_to(
"releaseset",
"Hydra::Schema::Releasesets",
{ name => "release", project => "project" },
);
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-27 14:48:09
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:t2ZI1kBn/GsKlY0e4+Wspg
# You can replace this text with custom content, and it will be preserved on regeneration
1;

View file

@ -0,0 +1,37 @@
package Hydra::Schema::Releasesets;
use strict;
use warnings;
use base 'DBIx::Class';
__PACKAGE__->load_components("Core");
__PACKAGE__->table("ReleaseSets");
__PACKAGE__->add_columns(
"project",
{ data_type => "text", is_nullable => 0, size => undef },
"name",
{ data_type => "text", is_nullable => 0, size => undef },
"description",
{ data_type => "text", is_nullable => 0, size => undef },
"keep",
{ data_type => "integer", is_nullable => 0, size => undef },
);
__PACKAGE__->set_primary_key("project", "name");
__PACKAGE__->belongs_to("project", "Hydra::Schema::Projects", { name => "project" });
__PACKAGE__->has_many(
"releasesetjobs",
"Hydra::Schema::Releasesetjobs",
{
"foreign.project" => "self.project",
"foreign.release" => "self.name",
},
);
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-27 14:48:09
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:pNqwNlXuENM/SsZ/utKhWw
# You can replace this text with custom content, and it will be preserved on regeneration
1;

View file

@ -16,8 +16,8 @@ __PACKAGE__->add_columns(
__PACKAGE__->set_primary_key("system");
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-27 03:26:23
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:90X5M27CbmJcZ7YnciHVMA
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-27 14:48:09
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:WeoKp84cptljEdtD+5l7Ug
# You can replace this text with custom content, and it will be preserved on regeneration

View file

@ -17,8 +17,8 @@ __PACKAGE__->set_primary_key("username", "role");
__PACKAGE__->belongs_to("username", "Hydra::Schema::Users", { username => "username" });
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-27 03:26:23
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:g2EVNE74pSi9teIFqIA92Q
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-27 14:48:09
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:WxjgPLWPvXpQ3nmxmlU7Dw
# You can replace this text with custom content, and it will be preserved on regeneration

View file

@ -25,8 +25,8 @@ __PACKAGE__->has_many(
);
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-27 03:26:23
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:gmqkPkkET+452wBlILgOsQ
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-27 14:48:09
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:s+M14nuDVIMoRSgXodj3dw
# You can replace this text with custom content, and it will be preserved on regeneration

View file

@ -276,7 +276,7 @@ print STDERR "performing build $buildId\n";
# have the lock taken away.
my $build;
$db->txn_do(sub {
($build) = $db->resultset('Builds')->search({id => $buildId});
$build = $db->resultset('Builds')->find($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";

View file

@ -1,7 +1,7 @@
[% WRAPPER layout.tt title="Job Status" %]
[% PROCESS common.tt %]
<h1>Job Status[% IF curProject %] in Project <tt>[% curProject.name %]</tt>[% END %]</h1>
<h1>Job Status[% IF curProject %] of Project <tt>[% curProject.name %]</tt>[% END %]</h1>
<p>Below are the latest builds for each job.</p>

View file

@ -99,9 +99,9 @@
<div class="title"><a href="[% c.uri_for('/project' project.name) %]">[% HTML.escape(project.displayname) %]</a></div>
[% IF curProject.name == project.name %]
<ul class="subsubmenu">
[% INCLUDE makeLink uri = c.uri_for('/project' project.name 'edit') title = "Edit" %]
[% INCLUDE makeLink uri = c.uri_for('/project' project.name 'jobstatus') title = "Job status" %]
[% INCLUDE makeLink uri = c.uri_for('/project' project.name 'all') title = "All builds" %]
[% INCLUDE makeLink uri = c.uri_for('/project' project.name 'edit') title = "Edit" %]
</ul>
[% END %]
</li>

View file

@ -96,7 +96,11 @@
<tr>
<th>Last checked:</th>
<td>
[% PROCESS renderDateTime timestamp = jobset.lastcheckedtime %]
[% IF jobset.lastcheckedtime %]
[% PROCESS renderDateTime timestamp = jobset.lastcheckedtime %]
[% ELSE %]
<em>never</em>
[% END %]
</td>
</tr>
[% END %]

View file

@ -0,0 +1,60 @@
[% WRAPPER layout.tt title="Releases" %]
[% PROCESS common.tt %]
[% USE HTML %]
<h1>Releases</h1>
<!-- <p>Description: [% releaseSet.description %]</p> -->
<table class="tablesorter">
<thead>
<tr>
<th></th>
<th>#</th>
<th>Release</th>
[% FOREACH job IN jobs %]
<th>[% IF job.description; HTML.escape(job.description); ELSE %]<tt>[% job.job %]</tt> ([% job.attrs %])[% END %]</th>
[% END %]
</tr>
</thead>
<tbody>
[% FOREACH release IN releases %]
<tr>
<td>
[% IF release.status == 0 %]
<img src="/static/images/success.gif" />
[% ELSIF release.status == 1 %]
<img src="/static/images/failure.gif" />
[% ELSIF release.status == 2 %]
<img src="/static/images/question-mark.png" />
[% END %]
</td>
<td>[% release.id %]</td>
<td>
[% IF release.releasename %]
<tt>[% release.releasename %]</tt>
[% ELSE %]
<em>No name</em>
[% END %]
</td>
[% FOREACH job IN release.jobs %]
<td>
[% IF job.build %]
<a href="[% c.uri_for('/build' job.build.id) %]">
[% IF job.build.resultInfo.buildstatus == 0 %]
<img src="/static/images/success.gif" />
[% ELSE %]
<img src="/static/images/failure.gif" />
[% END %]
[% job.build.id %]
</a>
[% END %]
</td>
[% END %]
</tr>
[% END %]
</tbody>
</table>
[% END %]

View file

@ -0,0 +1,14 @@
[% WRAPPER layout.tt title="Release Sets" %]
[% PROCESS common.tt %]
<h1>Release Sets</h1>
<p>Project <tt>[% curProject.name %]</tt> has the following release sets:</p>
<ul>
[% FOREACH releaseSet IN releaseSets %]
<li><a href="[% c.uri_for('/releases' curProject.name releaseSet.name) %]"><tt>[% releaseSet.name %]</tt></a></li>
[% END %]
</ul>
[% END %]

Binary file not shown.

After

Width:  |  Height:  |  Size: 586 B

View file

@ -168,6 +168,8 @@ create trigger cascadeProjectUpdate
update JobsetInputs set project = new.name where project = old.name;
update JobsetInputAlts set project = new.name where project = old.name;
update Builds set project = new.name where project = old.name;
update ReleaseSets set project = new.name where project = old.name;
update ReleaseSetJobs set project = new.name where project = old.name;
end;
@ -288,3 +290,64 @@ create trigger cascadeUserDelete
for each row begin
delete from UserRoles where userName = old.userName;
end;
-- Release sets are a mechanism to automatically group related builds
-- together. A release set defines what an individual release
-- consists of, namely: a release consists of a build of some
-- "primary" job, plus all builds of the other jobs named in
-- ReleaseSetJobs that have that build as an input. If there are
-- multiple builds matching a ReleaseSetJob, then we take the *oldest*
-- successful build (for release stability), or the *newest*
-- unsuccessful build if there is no succesful build. A release is
-- itself considered successful if all builds (except those for jobs
-- that have mayFail set) are successful.
--
-- Note that individual releases aren't separately stored in the
-- database, so they're really just a dynamic view on the universe of
-- builds, defined by a ReleaseSet.
create table ReleaseSets (
project text not null,
name text not null,
description text,
-- If true, don't garbage-collect builds belonging to the releases
-- defined by this row.
keep integer not null default 0,
primary key (project, name),
foreign key (project) references Projects(name) on delete cascade -- ignored by sqlite
);
create trigger cascadeReleaseSetDelete
before delete on ReleaseSets
for each row begin
delete from ReleaseSetJobs where project = old.project and release = old.release;
end;
create table ReleaseSetJobs (
project text not null,
release text not null,
job text not null,
-- A constraint on the job consisting of `name=value' pairs,
-- e.g. "system=i686-linux officialRelease=true". Should really
-- be a separate table but I'm lazy.
attrs text not null,
-- If set, this is the primary job for the release. There can be
-- onlyt one such job per release set.
isPrimary integer not null default 0,
mayFail integer not null default 0,
description text,
primary key (project, release, job, attrs),
foreign key (project) references Projects(name) on delete cascade, -- ignored by sqlite
foreign key (project, release) references ReleaseSets(project, name) on delete cascade -- ignored by sqlite
);