resolves #36: queue/start/run

pull/53/head
Oliver Giles 6 years ago
parent 828b66682d
commit f1e4d10be3

@ -104,16 +104,35 @@ chmod +x /var/lib/laminar/cfg/hello.run
# Triggering a run
To trigger a run of the `hello` job, execute
When triggering a run, the job is first added to a queue of upcoming tasks. If the server is busy, the job may wait in this queue for a while. It will only be assigned a job number when it leaves this queue and starts executing. The job number may be useful to the client that triggers the run, so there are a few ways to trigger a run.
To add the `hello` job to the queue ("fire-and-forget"), execute
```bash
laminarc queue hello
```
In this case, laminarc returns immediately, with its error code indicating whether adding the job to the queue was sucessful.
To queue the job and wait until it leaves the queue and starts executing, use
```bash
laminarc trigger hello
laminarc start hello
```
This causes the `/var/lib/laminar/cfg/hello.run` script to be executed, with a working directory of `/var/lib/laminar/run/hello/1`
In this case, laminarc blocks until the job starts executing, or returns immediately if queueing failed. The run number will be printed to standard output.
To launch and run the `hello` job to completion, execute
```bash
laminarc run hello
```
In all cases, a started run means the `/var/lib/laminar/cfg/hello.run` script will be executed, with a working directory of `/var/lib/laminar/run/hello/1` (or current run number)
The result and log output should be visible in the Web UI at http://localhost:8080/jobs/hello/1
## Isn't there a "Build Now" button I can click?
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.

@ -84,16 +84,16 @@ int main(int argc, char** argv) {
auto& waitScope = client.getWaitScope();
if(strcmp(argv[1], "trigger") == 0) {
if(strcmp(argv[1], "queue") == 0) {
if(argc < 3) {
fprintf(stderr, "Usage %s trigger <jobName>\n", argv[0]);
fprintf(stderr, "Usage %s queue <jobName>\n", argv[0]);
return EINVAL;
}
kj::Vector<capnp::RemotePromise<LaminarCi::TriggerResults>> promises;
kj::Vector<capnp::RemotePromise<LaminarCi::QueueResults>> promises;
int jobNameIndex = 2;
// make a request for each job specified on the commandline
do {
auto req = laminar.triggerRequest();
auto req = laminar.queueRequest();
req.setJobName(argv[jobNameIndex]);
int n = setParams(argc - jobNameIndex - 1, &argv[jobNameIndex + 1], req);
promises.add(req.send());
@ -106,13 +106,38 @@ int main(int argc, char** argv) {
return ENOENT;
}
}
} else if(strcmp(argv[1], "run") == 0 || strcmp(argv[1], "start") == 0) {
} else if(strcmp(argv[1], "start") == 0 || strcmp(argv[1], "trigger") == 0) {
if(strcmp(argv[1], "trigger") == 0)
fprintf(stderr, "Warning: 'trigger' is deprecated, use 'queue' for the old behavior\n");
if(argc < 3) {
fprintf(stderr, "Usage %s run <jobName>\n", argv[0]);
fprintf(stderr, "Usage %s queue <jobName>\n", argv[0]);
return EINVAL;
}
if(strcmp(argv[1], "start") == 0) {
fprintf(stderr, "Warning: \"start\" is deprecated, please use \"run\" instead\n");
kj::Vector<capnp::RemotePromise<LaminarCi::StartResults>> promises;
struct: public kj::TaskSet::ErrorHandler {
void taskFailed(kj::Exception&&) override {}
} ignoreFailed;
kj::TaskSet ts(ignoreFailed);
int jobNameIndex = 2;
// make a request for each job specified on the commandline
do {
auto req = laminar.startRequest();
req.setJobName(argv[jobNameIndex]);
int n = setParams(argc - jobNameIndex - 1, &argv[jobNameIndex + 1], req);
ts.add(req.send().then([&ret,argv,jobNameIndex](capnp::Response<LaminarCi::StartResults> resp){
if(resp.getResult() != LaminarCi::MethodResult::SUCCESS) {
fprintf(stderr, "Failed to start job '%s'\n", argv[2]);
ret = ENOENT;
}
printf("%s:%d\n", argv[jobNameIndex], resp.getBuildNum());
}));
jobNameIndex += n + 1;
} while(jobNameIndex < argc);
ts.onEmpty().wait(waitScope);
} else if(strcmp(argv[1], "run") == 0) {
if(argc < 3) {
fprintf(stderr, "Usage %s run <jobName>\n", argv[0]);
return EINVAL;
}
struct: public kj::TaskSet::ErrorHandler {
void taskFailed(kj::Exception&&) override {}

@ -2,11 +2,12 @@
interface LaminarCi {
trigger @0 (jobName :Text, params :List(JobParam)) -> (result :MethodResult);
run @1 (jobName :Text, params :List(JobParam)) -> (result :JobResult, buildNum :UInt32);
set @2 (jobName :Text, buildNum :UInt32, param :JobParam) -> (result :MethodResult);
lock @3 (lockName :Text) -> ();
release @4 (lockName :Text) -> ();
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);
lock @4 (lockName :Text) -> ();
release @5 (lockName :Text) -> ();
struct JobParam {
name @0 :Text;

@ -712,6 +712,9 @@ void Laminar::assignNewJobs() {
c->sendMessage(msg);
}
// notify the rpc client if the start command was used
run->started.fulfiller->fulfill();
// setup run completion handler
run->notifyCompletion = [this](Run* r) { runFinished(r); };

@ -92,6 +92,7 @@ public:
int fd;
std::unordered_map<std::string, std::string> params;
kj::Promise<void> timeout = kj::NEVER_DONE;
kj::PromiseFulfillerPair<void> started = kj::newPromiseAndFulfiller<void>();
time_t queuedAt;
time_t startedAt;

@ -90,10 +90,10 @@ public:
laminar.deregisterWaiter(this);
}
// Start a job, without waiting for it to finish
kj::Promise<void> trigger(TriggerContext context) override {
// Queue a job, without waiting for it to start
kj::Promise<void> queue(QueueContext context) override {
std::string jobName = context.getParams().getJobName();
LLOG(INFO, "RPC trigger", jobName);
LLOG(INFO, "RPC queue", jobName);
ParamMap params;
for(auto p : context.getParams().getParams()) {
params[p.getName().cStr()] = p.getValue().cStr();
@ -105,6 +105,26 @@ public:
return kj::READY_NOW;
}
// Start a job, without waiting for it to finish
kj::Promise<void> start(StartContext context) override {
std::string jobName = context.getParams().getJobName();
LLOG(INFO, "RPC start", jobName);
ParamMap params;
for(auto p : context.getParams().getParams()) {
params[p.getName().cStr()] = p.getValue().cStr();
}
std::shared_ptr<Run> run = laminar.queueJob(jobName, params);
if(Run* r = run.get()) {
return r->started.promise.then([context,r]() mutable {
context.getResults().setResult(LaminarCi::MethodResult::SUCCESS);
context.getResults().setBuildNum(r->build);
});
} else {
context.getResults().setResult(LaminarCi::MethodResult::FAILED);
return kj::READY_NOW;
}
}
// Start a job and wait for the result
kj::Promise<void> run(RunContext context) override {
std::string jobName = context.getParams().getJobName();
@ -115,11 +135,10 @@ public:
}
std::shared_ptr<Run> run = laminar.queueJob(jobName, params);
if(const Run* r = run.get()) {
uint num = r->build;
runWaiters[r].emplace_back(kj::newPromiseAndFulfiller<RunState>());
return runWaiters[r].back().promise.then([context,num](RunState state) mutable {
return runWaiters[r].back().promise.then([context,run](RunState state) mutable {
context.getResults().setResult(fromRunState(state));
context.getResults().setBuildNum(num);
context.getResults().setBuildNum(run->build);
});
} else {
context.getResults().setResult(LaminarCi::JobResult::UNKNOWN);

@ -98,8 +98,8 @@ protected:
Server* server;
};
TEST_F(ServerTest, RpcTrigger) {
auto req = client().triggerRequest();
TEST_F(ServerTest, RpcQueue) {
auto req = client().queueRequest();
req.setJobName("foo");
EXPECT_CALL(mockLaminar, queueJob("foo", ParamMap())).Times(testing::Exactly(1));
req.send().wait(ws());

Loading…
Cancel
Save