forked from lix-project/hydra
hydra-evaluator: deal in jobset IDs
This commit is contained in:
parent
cb01859718
commit
54341cd9f6
|
@ -13,7 +13,57 @@
|
||||||
|
|
||||||
using namespace nix;
|
using namespace nix;
|
||||||
|
|
||||||
typedef std::pair<std::string, std::string> JobsetIdentity;
|
typedef std::pair<std::string, std::string> JobsetSymbolicIdentity;
|
||||||
|
|
||||||
|
class JobsetIdentity {
|
||||||
|
public:
|
||||||
|
|
||||||
|
std::string project;
|
||||||
|
std::string jobset;
|
||||||
|
int id;
|
||||||
|
|
||||||
|
|
||||||
|
JobsetIdentity(const std::string& project, const std::string& jobset, const int id)
|
||||||
|
: project{ project }, jobset{ jobset }, id{ id }
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
friend bool operator== (const JobsetIdentity &lhs, const JobsetIdentity &rhs);
|
||||||
|
friend bool operator!= (const JobsetIdentity &lhs, const JobsetIdentity &rhs);
|
||||||
|
friend bool operator< (const JobsetIdentity &lhs, const JobsetIdentity &rhs);
|
||||||
|
|
||||||
|
|
||||||
|
friend bool operator== (const JobsetIdentity &lhs, const JobsetSymbolicIdentity &rhs);
|
||||||
|
friend bool operator!= (const JobsetIdentity &lhs, const JobsetSymbolicIdentity &rhs);
|
||||||
|
|
||||||
|
std::string display() const {
|
||||||
|
return str(format("%1%:%2% (jobset#%3%)") % project % jobset % id);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
bool operator==(const JobsetIdentity & lhs, const JobsetIdentity & rhs)
|
||||||
|
{
|
||||||
|
return lhs.id == rhs.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator!=(const JobsetIdentity & lhs, const JobsetIdentity & rhs)
|
||||||
|
{
|
||||||
|
return lhs.id != rhs.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator<(const JobsetIdentity & lhs, const JobsetIdentity & rhs)
|
||||||
|
{
|
||||||
|
return lhs.id < rhs.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator==(const JobsetIdentity & lhs, const JobsetSymbolicIdentity & rhs)
|
||||||
|
{
|
||||||
|
return lhs.project == rhs.first && lhs.jobset == rhs.second;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator!=(const JobsetIdentity & lhs, const JobsetSymbolicIdentity & rhs)
|
||||||
|
{
|
||||||
|
return ! (lhs == rhs);
|
||||||
|
}
|
||||||
|
|
||||||
enum class EvaluationStyle
|
enum class EvaluationStyle
|
||||||
{
|
{
|
||||||
|
@ -39,7 +89,7 @@ struct Evaluator
|
||||||
|
|
||||||
typedef std::map<JobsetIdentity, Jobset> Jobsets;
|
typedef std::map<JobsetIdentity, Jobset> Jobsets;
|
||||||
|
|
||||||
std::optional<JobsetIdentity> evalOne;
|
std::optional<JobsetSymbolicIdentity> evalOne;
|
||||||
|
|
||||||
const size_t maxEvals;
|
const size_t maxEvals;
|
||||||
|
|
||||||
|
@ -68,7 +118,7 @@ struct Evaluator
|
||||||
pqxx::work txn(*conn);
|
pqxx::work txn(*conn);
|
||||||
|
|
||||||
auto res = txn.exec
|
auto res = txn.exec
|
||||||
("select project, j.name, lastCheckedTime, triggerTime, checkInterval, j.enabled as jobset_enabled "
|
("select j.id as id, project, j.name, lastCheckedTime, triggerTime, checkInterval, j.enabled as jobset_enabled "
|
||||||
"from Jobsets j "
|
"from Jobsets j "
|
||||||
"join Projects p on j.project = p.name "
|
"join Projects p on j.project = p.name "
|
||||||
"where j.enabled != 0 and p.enabled != 0");
|
"where j.enabled != 0 and p.enabled != 0");
|
||||||
|
@ -79,7 +129,7 @@ struct Evaluator
|
||||||
std::set<JobsetIdentity> seen;
|
std::set<JobsetIdentity> seen;
|
||||||
|
|
||||||
for (auto const & row : res) {
|
for (auto const & row : res) {
|
||||||
auto name = JobsetIdentity{row["project"].as<std::string>(), row["name"].as<std::string>()};
|
auto name = JobsetIdentity{row["project"].as<std::string>(), row["name"].as<std::string>(), row["id"].as<int>()};
|
||||||
|
|
||||||
if (evalOne && name != *evalOne) continue;
|
if (evalOne && name != *evalOne) continue;
|
||||||
|
|
||||||
|
@ -113,7 +163,7 @@ struct Evaluator
|
||||||
if (seen.count(i->first))
|
if (seen.count(i->first))
|
||||||
++i;
|
++i;
|
||||||
else {
|
else {
|
||||||
printInfo("forgetting jobset ‘%s:%s’", i->first.first, i->first.second);
|
printInfo("forgetting jobset ‘%s’", i->first.display());
|
||||||
i = state->jobsets.erase(i);
|
i = state->jobsets.erase(i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -122,25 +172,24 @@ struct Evaluator
|
||||||
{
|
{
|
||||||
time_t now = time(0);
|
time_t now = time(0);
|
||||||
|
|
||||||
printInfo("starting evaluation of jobset ‘%s:%s’ (last checked %d s ago)",
|
printInfo("starting evaluation of jobset ‘%s’ (last checked %d s ago)",
|
||||||
jobset.name.first, jobset.name.second,
|
jobset.name.display(),
|
||||||
now - jobset.lastCheckedTime);
|
now - jobset.lastCheckedTime);
|
||||||
|
|
||||||
{
|
{
|
||||||
auto conn(dbPool.get());
|
auto conn(dbPool.get());
|
||||||
pqxx::work txn(*conn);
|
pqxx::work txn(*conn);
|
||||||
txn.exec_params0
|
txn.exec_params0
|
||||||
("update Jobsets set startTime = $1 where project = $2 and name = $3",
|
("update Jobsets set startTime = $1 where id = $2",
|
||||||
now,
|
now,
|
||||||
jobset.name.first,
|
jobset.name.id);
|
||||||
jobset.name.second);
|
|
||||||
txn.commit();
|
txn.commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(jobset.pid == -1);
|
assert(jobset.pid == -1);
|
||||||
|
|
||||||
jobset.pid = startProcess([&]() {
|
jobset.pid = startProcess([&]() {
|
||||||
Strings args = { "hydra-eval-jobset", jobset.name.first, jobset.name.second };
|
Strings args = { "hydra-eval-jobset", jobset.name.project, jobset.name.jobset };
|
||||||
execvp(args.front().c_str(), stringsToCharPtrs(args).data());
|
execvp(args.front().c_str(), stringsToCharPtrs(args).data());
|
||||||
throw SysError("executing ‘%1%’", args.front());
|
throw SysError("executing ‘%1%’", args.front());
|
||||||
});
|
});
|
||||||
|
@ -154,23 +203,23 @@ struct Evaluator
|
||||||
{
|
{
|
||||||
if (jobset.pid != -1) {
|
if (jobset.pid != -1) {
|
||||||
// Already running.
|
// Already running.
|
||||||
debug("shouldEvaluate %s:%s? no: already running",
|
debug("shouldEvaluate %s? no: already running",
|
||||||
jobset.name.first, jobset.name.second);
|
jobset.name.display());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (jobset.triggerTime != std::numeric_limits<time_t>::max()) {
|
if (jobset.triggerTime != std::numeric_limits<time_t>::max()) {
|
||||||
// An evaluation of this Jobset is requested
|
// An evaluation of this Jobset is requested
|
||||||
debug("shouldEvaluate %s:%s? yes: requested",
|
debug("shouldEvaluate %s? yes: requested",
|
||||||
jobset.name.first, jobset.name.second);
|
jobset.name.display());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (jobset.checkInterval <= 0) {
|
if (jobset.checkInterval <= 0) {
|
||||||
// Automatic scheduling is disabled. We allow requested
|
// Automatic scheduling is disabled. We allow requested
|
||||||
// evaluations, but never schedule start one.
|
// evaluations, but never schedule start one.
|
||||||
debug("shouldEvaluate %s:%s? no: checkInterval <= 0",
|
debug("shouldEvaluate %s? no: checkInterval <= 0",
|
||||||
jobset.name.first, jobset.name.second);
|
jobset.name.display());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -186,16 +235,15 @@ struct Evaluator
|
||||||
if (jobset.evaluation_style == EvaluationStyle::ONE_AT_A_TIME) {
|
if (jobset.evaluation_style == EvaluationStyle::ONE_AT_A_TIME) {
|
||||||
auto evaluation_res = txn.parameterized
|
auto evaluation_res = txn.parameterized
|
||||||
("select id from JobsetEvals "
|
("select id from JobsetEvals "
|
||||||
"where project = $1 and jobset = $2 "
|
"where jobset_id = $1 "
|
||||||
"order by id desc limit 1")
|
"order by id desc limit 1")
|
||||||
(jobset.name.first)
|
(jobset.name.id)
|
||||||
(jobset.name.second)
|
|
||||||
.exec();
|
.exec();
|
||||||
|
|
||||||
if (evaluation_res.empty()) {
|
if (evaluation_res.empty()) {
|
||||||
// First evaluation, so allow scheduling.
|
// First evaluation, so allow scheduling.
|
||||||
debug("shouldEvaluate(one-at-a-time) %s:%s? yes: no prior eval",
|
debug("shouldEvaluate(one-at-a-time) %s? yes: no prior eval",
|
||||||
jobset.name.first, jobset.name.second);
|
jobset.name.display());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -214,20 +262,20 @@ struct Evaluator
|
||||||
// If the previous evaluation has no unfinished builds
|
// If the previous evaluation has no unfinished builds
|
||||||
// schedule!
|
// schedule!
|
||||||
if (unfinished_build_res.empty()) {
|
if (unfinished_build_res.empty()) {
|
||||||
debug("shouldEvaluate(one-at-a-time) %s:%s? yes: no unfinished builds",
|
debug("shouldEvaluate(one-at-a-time) %s? yes: no unfinished builds",
|
||||||
jobset.name.first, jobset.name.second);
|
jobset.name.display());
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
debug("shouldEvaluate(one-at-a-time) %s:%s? no: at least one unfinished build",
|
debug("shouldEvaluate(one-at-a-time) %s:%s? no: at least one unfinished build",
|
||||||
jobset.name.first, jobset.name.second);
|
jobset.name.display());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// EvaluationStyle::ONESHOT, EvaluationStyle::SCHEDULED
|
// EvaluationStyle::ONESHOT, EvaluationStyle::SCHEDULED
|
||||||
debug("shouldEvaluate(oneshot/scheduled) %s:%s? yes: checkInterval elapsed",
|
debug("shouldEvaluate(oneshot/scheduled) %s? yes: checkInterval elapsed",
|
||||||
jobset.name.first, jobset.name.second);
|
jobset.name.display());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -352,8 +400,8 @@ struct Evaluator
|
||||||
auto & jobset(i.second);
|
auto & jobset(i.second);
|
||||||
|
|
||||||
if (jobset.pid == pid) {
|
if (jobset.pid == pid) {
|
||||||
printInfo("evaluation of jobset ‘%s:%s’ %s",
|
printInfo("evaluation of jobset ‘%s’ %s",
|
||||||
jobset.name.first, jobset.name.second, statusToString(status));
|
jobset.name.display(), statusToString(status));
|
||||||
|
|
||||||
auto now = time(0);
|
auto now = time(0);
|
||||||
|
|
||||||
|
@ -369,23 +417,20 @@ struct Evaluator
|
||||||
jobset from getting stuck in an endless
|
jobset from getting stuck in an endless
|
||||||
failing eval loop. */
|
failing eval loop. */
|
||||||
txn.exec_params0
|
txn.exec_params0
|
||||||
("update Jobsets set triggerTime = null where project = $1 and name = $2 and startTime is not null and triggerTime <= startTime",
|
("update Jobsets set triggerTime = null where id = $1 and startTime is not null and triggerTime <= startTime",
|
||||||
jobset.name.first,
|
jobset.name.id);
|
||||||
jobset.name.second);
|
|
||||||
|
|
||||||
/* Clear the start time. */
|
/* Clear the start time. */
|
||||||
txn.exec_params0
|
txn.exec_params0
|
||||||
("update Jobsets set startTime = null where project = $1 and name = $2",
|
("update Jobsets set startTime = null where id = $1",
|
||||||
jobset.name.first,
|
jobset.name.id);
|
||||||
jobset.name.second);
|
|
||||||
|
|
||||||
if (!WIFEXITED(status) || WEXITSTATUS(status) > 1) {
|
if (!WIFEXITED(status) || WEXITSTATUS(status) > 1) {
|
||||||
txn.exec_params0
|
txn.exec_params0
|
||||||
("update Jobsets set errorMsg = $1, lastCheckedTime = $2, errorTime = $2, fetchErrorMsg = null where project = $3 and name = $4",
|
("update Jobsets set errorMsg = $1, lastCheckedTime = $2, errorTime = $2, fetchErrorMsg = null where id = $3",
|
||||||
fmt("evaluation %s", statusToString(status)),
|
fmt("evaluation %s", statusToString(status)),
|
||||||
now,
|
now,
|
||||||
jobset.name.first,
|
jobset.name.id);
|
||||||
jobset.name.second);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
txn.commit();
|
txn.commit();
|
||||||
|
@ -466,7 +511,7 @@ int main(int argc, char * * argv)
|
||||||
else {
|
else {
|
||||||
if (!args.empty()) {
|
if (!args.empty()) {
|
||||||
if (args.size() != 2) throw UsageError("Syntax: hydra-evaluator [<project> <jobset>]");
|
if (args.size() != 2) throw UsageError("Syntax: hydra-evaluator [<project> <jobset>]");
|
||||||
evaluator.evalOne = JobsetIdentity(args[0], args[1]);
|
evaluator.evalOne = JobsetSymbolicIdentity(args[0], args[1]);
|
||||||
}
|
}
|
||||||
evaluator.run();
|
evaluator.run();
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue