resolves #67: laminarc list jobs

Implements the following laminarc commands:
- show-jobs
- show-running
- show-queued
- abort
pull/70/head
Oliver Giles 6 years ago
parent 21284731a3
commit 7cee824cee

@ -142,6 +142,14 @@ laminarc queue test-host test-target
This is against the design principles of Laminar and was deliberately excluded. Laminar's web UI is strictly read-only, making it simple to deploy in mixed-permission or public environments without an authentication layer. Furthermore, Laminar tries to encourage ideal continuous integration, where manual triggering is an anti-pattern. Want to make a release? Push a git tag and implement a post-receive hook. Want to re-run a build due to sporadic failure/flaky tests? Fix the tests locally and push a patch. Experience shows that a manual trigger such as a "Build Now" button is often used as a crutch to avoid doing the correct thing, negatively impacting traceability and quality.
## Listing jobs from the command line
`laminarc` may be used to inspect the server state:
- `laminarc show-jobs`: Lists all files matching `/var/lib/laminar/cfg/jobs/*.run` on the server side.
- `laminarc show-running`: Lists all currently running jobs and their run numbers.
- `laminarc show-queued`: Lists all jobs waiting in the queue.
## Triggering a job at a certain time
This is what `cron` is for. To trigger a build of `hello` every day at 0300, add
@ -433,7 +441,9 @@ make -C src
---
# Abort on timeout
# Aborting running jobs
## After a timeout
To configure a maximum execution time in seconds for a job, add a line to `/var/lib/laminar/cfg/jobs/JOBNAME.conf`:
@ -441,6 +451,10 @@ To configure a maximum execution time in seconds for a job, add a line to `/var/
TIMEOUT=120
```
## Manually
`laminarc abort $JOBNAME $NUMBER`
---
# Nodes and Tags
@ -607,5 +621,9 @@ Finally, variables supplied on the command-line call to `laminarc queue`, `lamin
- `start [JOB [PARAMS...]]...` starts one or more jobs with optional parameters, returning when the jobs begin execution.
- `run [JOB [PARAMS...]]...` triggers one or more jobs with optional parameters and waits for the completion of all jobs. Returns a non-zero error code if any job failed.
- `set [VARIABLE=VALUE]...` sets one or more variables to be exported in subsequent scripts for the run identified by the `$JOB` and `$RUN` environment variables
- `show-jobs` shows the known jobs on the server (`$LAMINAR_HOME/cfg/jobs/*.run`).
- `show-running` shows the currently running jobs with their numbers.
- `show-queued` shows the names of the jobs waiting in the queue.
- `abort JOB NUMBER` manually aborts a currently running job by name and number.
`laminarc` connects to `laminard` using the address supplied by the `LAMINAR_HOST` environment variable. If it is not set, `laminarc` will first attempt to use `LAMINAR_BIND_RPC`, which will be available if `laminarc` is executed from a script within `laminard`. If neither `LAMINAR_HOST` nor `LAMINAR_BIND_RPC` is set, `laminarc` will assume a default host of `unix-abstract:laminar`.

@ -173,8 +173,8 @@ int main(int argc, char** argv) {
char* name = argv[2];
*eq++ = '\0';
char* val = eq;
req.setJobName(job);
req.setBuildNum(atoi(num));
req.getRun().setJob(job);
req.getRun().setBuildNum(atoi(num));
req.getParam().setName(name);
req.getParam().setValue(val);
req.send().wait(waitScope);
@ -182,6 +182,40 @@ int main(int argc, char** argv) {
fprintf(stderr, "Missing $JOB or $RUN or param is not in the format key=value\n");
return EINVAL;
}
} else if(strcmp(argv[1], "abort") == 0) {
if(argc != 4) {
fprintf(stderr, "Usage %s abort <jobName> <jobNumber>\n", argv[0]);
return EINVAL;
}
auto req = laminar.abortRequest();
req.getRun().setJob(argv[2]);
req.getRun().setBuildNum(atoi(argv[3]));
if(req.send().wait(waitScope).getResult() != LaminarCi::MethodResult::SUCCESS)
ret = EFAILED;
} else if(strcmp(argv[1], "show-jobs") == 0) {
if(argc != 2) {
fprintf(stderr, "Usage: %s show-jobs\n", argv[0]);
return EINVAL;
}
for(auto it : laminar.listKnownRequest().send().wait(waitScope).getResult()) {
printf("%s\n", it.cStr());
}
} else if(strcmp(argv[1], "show-queued") == 0) {
if(argc != 2) {
fprintf(stderr, "Usage: %s show-queued\n", argv[0]);
return EINVAL;
}
for(auto it : laminar.listQueuedRequest().send().wait(waitScope).getResult()) {
printf("%s\n", it.cStr());
}
} else if(strcmp(argv[1], "show-running") == 0) {
if(argc != 2) {
fprintf(stderr, "Usage: %s show-running\n", argv[0]);
return EINVAL;
}
for(auto it : laminar.listRunningRequest().send().wait(waitScope).getResult()) {
printf("%s:%d\n", it.getJob().cStr(), it.getBuildNum());
}
} else {
fprintf(stderr, "Unknown command %s\n", argv[1]);
return EINVAL;

@ -128,6 +128,15 @@ struct LaminarInterface {
// the environment of subsequent scripts.
virtual bool setParam(std::string job, uint buildNum, std::string param, std::string value) = 0;
// Gets the list of jobs currently waiting in the execution queue
virtual const std::list<std::shared_ptr<Run>>& listQueuedJobs() = 0;
// Gets the list of currently executing jobs
virtual const RunSet& listRunningJobs() = 0;
// Gets the list of known jobs - scans cfg/jobs for *.run files
virtual std::list<std::string> listKnownJobs() = 0;
// Fetches the content of an artifact given its filename relative to
// $LAMINAR_HOME/archive. Ideally, this would instead be served by a
// proper web server which handles this url.
@ -143,6 +152,9 @@ struct LaminarInterface {
// which handles this url.
virtual std::string getCustomCss() = 0;
// Aborts a single job
virtual bool abort(std::string job, uint buildNum) = 0;
// Abort all running jobs
virtual void abortAll() = 0;

@ -5,7 +5,16 @@ interface LaminarCi {
queue @0 (jobName :Text, params :List(JobParam)) -> (result :MethodResult);
start @1 (jobName :Text, params :List(JobParam)) -> (result :MethodResult, buildNum :UInt32);
run @2 (jobName :Text, params :List(JobParam)) -> (result :JobResult, buildNum :UInt32);
set @3 (jobName :Text, buildNum :UInt32, param :JobParam) -> (result :MethodResult);
set @3 (run :Run, param :JobParam) -> (result :MethodResult);
listQueued @4 () -> (result :List(Text));
listRunning @5 () -> (result :List(Run));
listKnown @6 () -> (result :List(Text));
abort @7 (run :Run) -> (result :MethodResult);
struct Run {
job @0 :Text;
buildNum @1 :UInt32;
}
struct JobParam {
name @0 :Text;

@ -130,6 +130,25 @@ bool Laminar::setParam(std::string job, uint buildNum, std::string param, std::s
return false;
}
const std::list<std::shared_ptr<Run>>& Laminar::listQueuedJobs() {
return queuedJobs;
}
const RunSet& Laminar::listRunningJobs() {
return activeJobs;
}
std::list<std::string> Laminar::listKnownJobs() {
std::list<std::string> res;
KJ_IF_MAYBE(dir, fsHome->tryOpenSubdir(kj::Path{"cfg","jobs"})) {
for(kj::Directory::Entry& entry : (*dir)->listEntries()) {
if(entry.type == kj::FsNode::Type::FILE && entry.name.endsWith(".run")) {
res.emplace_back(entry.name.cStr(), entry.name.findLast('.').orDefault(0));
}
}
}
return res;
}
void Laminar::populateArtifacts(Json &j, std::string job, uint num) const {
kj::Path runArchive{job,std::to_string(num)};
@ -575,6 +594,14 @@ void Laminar::notifyConfigChanged()
assignNewJobs();
}
bool Laminar::abort(std::string job, uint buildNum) {
if(Run* run = activeRun(job, buildNum)) {
run->abort(true);
return true;
}
return false;
}
void Laminar::abortAll() {
for(std::shared_ptr<Run> run : activeJobs) {
run->abort(false);

@ -56,9 +56,13 @@ public:
void sendStatus(LaminarClient* client) override;
bool setParam(std::string job, uint buildNum, std::string param, std::string value) override;
const std::list<std::shared_ptr<Run>>& listQueuedJobs() override;
const RunSet& listRunningJobs() override;
std::list<std::string> listKnownJobs() override;
kj::Maybe<kj::Own<const kj::ReadableFile>> getArtefact(std::string path) override;
bool handleBadgeRequest(std::string job, std::string& badge) override;
std::string getCustomCss() override;
bool abort(std::string job, uint buildNum) override;
void abortAll() override;
void notifyConfigChanged() override;

@ -117,8 +117,8 @@ public:
// Set a parameter on a running build
kj::Promise<void> set(SetContext context) override {
std::string jobName = context.getParams().getJobName();
uint buildNum = context.getParams().getBuildNum();
std::string jobName = context.getParams().getRun().getJob();
uint buildNum = context.getParams().getRun().getBuildNum();
LLOG(INFO, "RPC set", jobName, buildNum);
LaminarCi::MethodResult result = laminar.setParam(jobName, buildNum,
@ -129,6 +129,52 @@ public:
return kj::READY_NOW;
}
// List jobs in queue
kj::Promise<void> listQueued(ListQueuedContext context) override {
const std::list<std::shared_ptr<Run>>& queue = laminar.listQueuedJobs();
auto res = context.getResults().initResult(queue.size());
int i = 0;
for(auto it : queue) {
res.set(i++, it->name);
}
return kj::READY_NOW;
}
// List running jobs
kj::Promise<void> listRunning(ListRunningContext context) override {
const RunSet& active = laminar.listRunningJobs();
auto res = context.getResults().initResult(active.size());
int i = 0;
for(auto it : active) {
res[i].setJob(it->name);
res[i].setBuildNum(it->build);
i++;
}
return kj::READY_NOW;
}
// List known jobs
kj::Promise<void> listKnown(ListKnownContext context) override {
std::list<std::string> known = laminar.listKnownJobs();
auto res = context.getResults().initResult(known.size());
int i = 0;
for(auto it : known) {
res.set(i++, it);
}
return kj::READY_NOW;
}
kj::Promise<void> abort(AbortContext context) override {
std::string jobName = context.getParams().getRun().getJob();
uint buildNum = context.getParams().getRun().getBuildNum();
LLOG(INFO, "RPC abort", jobName, buildNum);
LaminarCi::MethodResult result = laminar.abort(jobName, buildNum)
? LaminarCi::MethodResult::SUCCESS
: LaminarCi::MethodResult::FAILED;
context.getResults().setResult(result);
return kj::READY_NOW;
}
private:
// Helper to convert an RPC parameter list to a hash map
ParamMap params(const capnp::List<LaminarCi::JobParam>::Reader& paramReader) {

@ -49,8 +49,13 @@ public:
MOCK_METHOD1(deregisterWaiter, void(LaminarWaiter* waiter));
MOCK_METHOD1(sendStatus, void(LaminarClient* client));
MOCK_METHOD4(setParam, bool(std::string job, uint buildNum, std::string param, std::string value));
MOCK_METHOD0(listQueuedJobs, const std::list<std::shared_ptr<Run>>&());
MOCK_METHOD0(listRunningJobs, const RunSet&());
MOCK_METHOD0(listKnownJobs, std::list<std::string>());
MOCK_METHOD0(getCustomCss, std::string());
MOCK_METHOD2(handleBadgeRequest, bool(std::string, std::string&));
MOCK_METHOD2(abort, bool(std::string, uint));
MOCK_METHOD0(abortAll, void());
MOCK_METHOD0(notifyConfigChanged, void());
};

Loading…
Cancel
Save