forked from lix-project/hydra
Merge remote-tracking branch 'origin/master' into flake
This commit is contained in:
commit
4b5bb4e760
|
@ -147,9 +147,10 @@ static void worker(
|
||||||
nlohmann::json reply;
|
nlohmann::json reply;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
auto v = findAlongAttrPath(state, attrPath, autoArgs, *vRoot).first;
|
auto vTmp = findAlongAttrPath(state, attrPath, autoArgs, *vRoot).first;
|
||||||
|
|
||||||
state.forceValue(*v);
|
auto v = state.allocValue();
|
||||||
|
state.autoCallFunction(autoArgs, *vTmp, *v);
|
||||||
|
|
||||||
if (auto drv = getDerivation(state, *v, false)) {
|
if (auto drv = getDerivation(state, *v, false)) {
|
||||||
|
|
||||||
|
@ -231,6 +232,11 @@ static void worker(
|
||||||
reply["attrs"] = std::move(attrs);
|
reply["attrs"] = std::move(attrs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
else if (v->type == tNull)
|
||||||
|
;
|
||||||
|
|
||||||
|
else throw TypeError("attribute '%s' is %s, which is not supported", attrPath, showType(*v));
|
||||||
|
|
||||||
} catch (EvalError & e) {
|
} catch (EvalError & e) {
|
||||||
reply["error"] = filterANSIEscapes(e.msg(), true);
|
reply["error"] = filterANSIEscapes(e.msg(), true);
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,13 @@ using namespace nix;
|
||||||
|
|
||||||
typedef std::pair<std::string, std::string> JobsetName;
|
typedef std::pair<std::string, std::string> JobsetName;
|
||||||
|
|
||||||
|
enum class EvaluationStyle
|
||||||
|
{
|
||||||
|
SCHEDULE = 1,
|
||||||
|
ONESHOT = 2,
|
||||||
|
ONE_AT_A_TIME = 3,
|
||||||
|
};
|
||||||
|
|
||||||
struct Evaluator
|
struct Evaluator
|
||||||
{
|
{
|
||||||
std::unique_ptr<Config> config;
|
std::unique_ptr<Config> config;
|
||||||
|
@ -24,6 +31,7 @@ struct Evaluator
|
||||||
struct Jobset
|
struct Jobset
|
||||||
{
|
{
|
||||||
JobsetName name;
|
JobsetName name;
|
||||||
|
std::optional<EvaluationStyle> evaluation_style;
|
||||||
time_t lastCheckedTime, triggerTime;
|
time_t lastCheckedTime, triggerTime;
|
||||||
int checkInterval;
|
int checkInterval;
|
||||||
Pid pid;
|
Pid pid;
|
||||||
|
@ -60,7 +68,7 @@ struct Evaluator
|
||||||
pqxx::work txn(*conn);
|
pqxx::work txn(*conn);
|
||||||
|
|
||||||
auto res = txn.parameterized
|
auto res = txn.parameterized
|
||||||
("select project, j.name, lastCheckedTime, triggerTime, checkInterval from Jobsets j join Projects p on j.project = p.name "
|
("select project, j.name, lastCheckedTime, triggerTime, checkInterval, j.enabled as jobset_enabled from Jobsets j join Projects p on j.project = p.name "
|
||||||
"where j.enabled != 0 and p.enabled != 0").exec();
|
"where j.enabled != 0 and p.enabled != 0").exec();
|
||||||
|
|
||||||
auto state(state_.lock());
|
auto state(state_.lock());
|
||||||
|
@ -78,6 +86,17 @@ struct Evaluator
|
||||||
jobset.lastCheckedTime = row["lastCheckedTime"].as<time_t>(0);
|
jobset.lastCheckedTime = row["lastCheckedTime"].as<time_t>(0);
|
||||||
jobset.triggerTime = row["triggerTime"].as<time_t>(notTriggered);
|
jobset.triggerTime = row["triggerTime"].as<time_t>(notTriggered);
|
||||||
jobset.checkInterval = row["checkInterval"].as<time_t>();
|
jobset.checkInterval = row["checkInterval"].as<time_t>();
|
||||||
|
switch (row["jobset_enabled"].as<int>(0)) {
|
||||||
|
case 1:
|
||||||
|
jobset.evaluation_style = EvaluationStyle::SCHEDULE;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
jobset.evaluation_style = EvaluationStyle::ONESHOT;
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
jobset.evaluation_style = EvaluationStyle::ONE_AT_A_TIME;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
seen.insert(name);
|
seen.insert(name);
|
||||||
}
|
}
|
||||||
|
@ -129,19 +148,100 @@ struct Evaluator
|
||||||
childStarted.notify_one();
|
childStarted.notify_one();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool shouldEvaluate(Jobset & jobset)
|
||||||
|
{
|
||||||
|
if (jobset.pid != -1) {
|
||||||
|
// Already running.
|
||||||
|
debug("shouldEvaluate %s:%s? no: already running",
|
||||||
|
jobset.name.first, jobset.name.second);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (jobset.triggerTime != std::numeric_limits<time_t>::max()) {
|
||||||
|
// An evaluation of this Jobset is requested
|
||||||
|
debug("shouldEvaluate %s:%s? yes: requested",
|
||||||
|
jobset.name.first, jobset.name.second);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (jobset.checkInterval <= 0) {
|
||||||
|
// Automatic scheduling is disabled. We allow requested
|
||||||
|
// evaluations, but never schedule start one.
|
||||||
|
debug("shouldEvaluate %s:%s? no: checkInterval <= 0",
|
||||||
|
jobset.name.first, jobset.name.second);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (jobset.lastCheckedTime + jobset.checkInterval <= time(0)) {
|
||||||
|
// Time to schedule a fresh evaluation. If the jobset
|
||||||
|
// is a ONE_AT_A_TIME jobset, ensure the previous jobset
|
||||||
|
// has no remaining, unfinished work.
|
||||||
|
|
||||||
|
auto conn(dbPool.get());
|
||||||
|
|
||||||
|
pqxx::work txn(*conn);
|
||||||
|
|
||||||
|
if (jobset.evaluation_style == EvaluationStyle::ONE_AT_A_TIME) {
|
||||||
|
auto evaluation_res = txn.parameterized
|
||||||
|
("select id from JobsetEvals "
|
||||||
|
"where project = $1 and jobset = $2 "
|
||||||
|
"order by id desc limit 1")
|
||||||
|
(jobset.name.first)
|
||||||
|
(jobset.name.second)
|
||||||
|
.exec();
|
||||||
|
|
||||||
|
if (evaluation_res.empty()) {
|
||||||
|
// First evaluation, so allow scheduling.
|
||||||
|
debug("shouldEvaluate(one-at-a-time) %s:%s? yes: no prior eval",
|
||||||
|
jobset.name.first, jobset.name.second);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto evaluation_id = evaluation_res[0][0].as<int>();
|
||||||
|
|
||||||
|
auto unfinished_build_res = txn.parameterized
|
||||||
|
("select id from Builds "
|
||||||
|
"join JobsetEvalMembers "
|
||||||
|
" on (JobsetEvalMembers.build = Builds.id) "
|
||||||
|
"where JobsetEvalMembers.eval = $1 "
|
||||||
|
" and builds.finished = 0 "
|
||||||
|
" limit 1")
|
||||||
|
(evaluation_id)
|
||||||
|
.exec();
|
||||||
|
|
||||||
|
// If the previous evaluation has no unfinished builds
|
||||||
|
// schedule!
|
||||||
|
if (unfinished_build_res.empty()) {
|
||||||
|
debug("shouldEvaluate(one-at-a-time) %s:%s? yes: no unfinished builds",
|
||||||
|
jobset.name.first, jobset.name.second);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
debug("shouldEvaluate(one-at-a-time) %s:%s? no: at least one unfinished build",
|
||||||
|
jobset.name.first, jobset.name.second);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// EvaluationStyle::ONESHOT, EvaluationStyle::SCHEDULED
|
||||||
|
debug("shouldEvaluate(oneshot/scheduled) %s:%s? yes: checkInterval elapsed",
|
||||||
|
jobset.name.first, jobset.name.second);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
void startEvals(State & state)
|
void startEvals(State & state)
|
||||||
{
|
{
|
||||||
std::vector<Jobsets::iterator> sorted;
|
std::vector<Jobsets::iterator> sorted;
|
||||||
|
|
||||||
time_t now = time(0);
|
|
||||||
|
|
||||||
/* Filter out jobsets that have been evaluated recently and have
|
/* Filter out jobsets that have been evaluated recently and have
|
||||||
not been triggered. */
|
not been triggered. */
|
||||||
for (auto i = state.jobsets.begin(); i != state.jobsets.end(); ++i)
|
for (auto i = state.jobsets.begin(); i != state.jobsets.end(); ++i)
|
||||||
if (evalOne ||
|
if (evalOne ||
|
||||||
(i->second.pid == -1 &&
|
(i->second.evaluation_style && shouldEvaluate(i->second)))
|
||||||
(i->second.triggerTime != std::numeric_limits<time_t>::max() ||
|
|
||||||
(i->second.checkInterval > 0 && i->second.lastCheckedTime + i->second.checkInterval <= now))))
|
|
||||||
sorted.push_back(i);
|
sorted.push_back(i);
|
||||||
|
|
||||||
/* Put jobsets in order of ascending trigger time, last checked
|
/* Put jobsets in order of ascending trigger time, last checked
|
||||||
|
|
|
@ -193,7 +193,8 @@ sub checkPath {
|
||||||
sub serveFile {
|
sub serveFile {
|
||||||
my ($c, $path) = @_;
|
my ($c, $path) = @_;
|
||||||
|
|
||||||
my $res = run(cmd => ["nix", "ls-store", "--store", getStoreUri(), "--json", "$path"]);
|
my $res = run(cmd => ["nix", "--experimental-features", "nix-command",
|
||||||
|
"ls-store", "--store", getStoreUri(), "--json", "$path"]);
|
||||||
|
|
||||||
if ($res->{status}) {
|
if ($res->{status}) {
|
||||||
notFound($c, "File '$path' does not exist.") if $res->{stderr} =~ /does not exist/;
|
notFound($c, "File '$path' does not exist.") if $res->{stderr} =~ /does not exist/;
|
||||||
|
@ -217,7 +218,8 @@ sub serveFile {
|
||||||
|
|
||||||
elsif ($ls->{type} eq "regular") {
|
elsif ($ls->{type} eq "regular") {
|
||||||
|
|
||||||
$c->stash->{'plain'} = { data => grab(cmd => ["nix", "cat-store", "--store", getStoreUri(), "$path"]) };
|
$c->stash->{'plain'} = { data => grab(cmd => ["nix", "--experimental-features", "nix-command",
|
||||||
|
"cat-store", "--store", getStoreUri(), "$path"]) };
|
||||||
|
|
||||||
# Detect MIME type. Borrowed from Catalyst::Plugin::Static::Simple.
|
# Detect MIME type. Borrowed from Catalyst::Plugin::Static::Simple.
|
||||||
my $type = "text/plain";
|
my $type = "text/plain";
|
||||||
|
|
|
@ -238,7 +238,7 @@ sub updateJobset {
|
||||||
}
|
}
|
||||||
|
|
||||||
my $enabled = int($c->stash->{params}->{enabled});
|
my $enabled = int($c->stash->{params}->{enabled});
|
||||||
die if $enabled < 0 || $enabled > 2;
|
die if $enabled < 0 || $enabled > 3;
|
||||||
|
|
||||||
my $shares = int($c->stash->{params}->{schedulingshares} // 1);
|
my $shares = int($c->stash->{params}->{schedulingshares} // 1);
|
||||||
error($c, "The number of scheduling shares must be positive.") if $shares <= 0;
|
error($c, "The number of scheduling shares must be positive.") if $shares <= 0;
|
||||||
|
|
|
@ -70,8 +70,14 @@ sub handleDeclarativeJobsetBuild {
|
||||||
my $id = $build->id;
|
my $id = $build->id;
|
||||||
die "Declarative jobset build $id failed" unless $build->buildstatus == 0;
|
die "Declarative jobset build $id failed" unless $build->buildstatus == 0;
|
||||||
my $declPath = ($build->buildoutputs)[0]->path;
|
my $declPath = ($build->buildoutputs)[0]->path;
|
||||||
my $declText = readNixFile($declPath)
|
my $declText = eval {
|
||||||
or die "Couldn't read declarative specification file $declPath: $!";
|
readNixFile($declPath)
|
||||||
|
};
|
||||||
|
if ($@) {
|
||||||
|
print STDERR "ERROR: failed to readNixFile $declPath: ", $@, "\n";
|
||||||
|
die;
|
||||||
|
}
|
||||||
|
|
||||||
my $declSpec = decode_json($declText);
|
my $declSpec = decode_json($declText);
|
||||||
txn_do($db, sub {
|
txn_do($db, sub {
|
||||||
my @kept = keys %$declSpec;
|
my @kept = keys %$declSpec;
|
||||||
|
|
|
@ -509,7 +509,8 @@ sub getStoreUri {
|
||||||
# Read a file from the (possibly remote) nix store
|
# Read a file from the (possibly remote) nix store
|
||||||
sub readNixFile {
|
sub readNixFile {
|
||||||
my ($path) = @_;
|
my ($path) = @_;
|
||||||
return grab(cmd => ["nix", "cat-store", "--store", getStoreUri(), "$path"]);
|
return grab(cmd => ["nix", "--experimental-features", "nix-command",
|
||||||
|
"cat-store", "--store", getStoreUri(), "$path"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -68,6 +68,7 @@
|
||||||
<input type="hidden" name="enabled" value="[% jobset.enabled %]" />
|
<input type="hidden" name="enabled" value="[% jobset.enabled %]" />
|
||||||
<button type="button" class="btn" value="1">Enabled</button>
|
<button type="button" class="btn" value="1">Enabled</button>
|
||||||
<button type="button" class="btn" value="2">One-shot</button>
|
<button type="button" class="btn" value="2">One-shot</button>
|
||||||
|
<button type="button" class="btn" value="3">One-at-a-time</button>
|
||||||
<button type="button" class="btn" value="0">Disabled</button>
|
<button type="button" class="btn" value="0">Disabled</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -129,7 +129,7 @@
|
||||||
<table class="info-table">
|
<table class="info-table">
|
||||||
<tr>
|
<tr>
|
||||||
<th>State:</th>
|
<th>State:</th>
|
||||||
<td>[% IF jobset.enabled == 0; "Disabled"; ELSIF jobset.enabled == 1; "Enabled"; ELSIF jobset.enabled == 2; "One-shot"; END %]</td>
|
<td>[% IF jobset.enabled == 0; "Disabled"; ELSIF jobset.enabled == 1; "Enabled"; ELSIF jobset.enabled == 2; "One-shot"; ELSIF jobset.enabled == 3; "One-at-a-time"; END %]</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Description:</th>
|
<th>Description:</th>
|
||||||
|
|
|
@ -82,7 +82,7 @@ sub getPath {
|
||||||
|
|
||||||
my $substituter = $config->{eval_substituter};
|
my $substituter = $config->{eval_substituter};
|
||||||
|
|
||||||
system("nix", "copy", "--from", $substituter, "--", $path)
|
system("nix", "--experimental-features", "nix-command", "copy", "--from", $substituter, "--", $path)
|
||||||
if defined $substituter;
|
if defined $substituter;
|
||||||
|
|
||||||
return isValidPath($path);
|
return isValidPath($path);
|
||||||
|
|
|
@ -61,7 +61,7 @@ create table Jobsets (
|
||||||
errorTime integer, -- timestamp associated with errorMsg
|
errorTime integer, -- timestamp associated with errorMsg
|
||||||
lastCheckedTime integer, -- last time the evaluator looked at this jobset
|
lastCheckedTime integer, -- last time the evaluator looked at this jobset
|
||||||
triggerTime integer, -- set if we were triggered by a push event
|
triggerTime integer, -- set if we were triggered by a push event
|
||||||
enabled integer not null default 1, -- 0 = disabled, 1 = enabled, 2 = one-shot
|
enabled integer not null default 1, -- 0 = disabled, 1 = enabled, 2 = one-shot, 3 = one-at-a-time
|
||||||
enableEmail integer not null default 1,
|
enableEmail integer not null default 1,
|
||||||
hidden integer not null default 0,
|
hidden integer not null default 0,
|
||||||
emailOverride text not null,
|
emailOverride text not null,
|
||||||
|
|
Loading…
Reference in a new issue