1
0
mirror of https://github.com/ohwgiles/laminar.git synced 2024-10-27 20:34:20 +00:00

resolves #36: queue/start/run

This commit is contained in:
Oliver Giles 2018-05-12 17:56:56 +03:00
parent 828b66682d
commit f1e4d10be3
7 changed files with 92 additions and 24 deletions

View File

@ -104,16 +104,35 @@ chmod +x /var/lib/laminar/cfg/hello.run
# Triggering a 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 ```bash
laminarc trigger hello laminarc queue 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 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 start hello
```
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 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? ## 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. 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.

View File

@ -84,16 +84,16 @@ int main(int argc, char** argv) {
auto& waitScope = client.getWaitScope(); auto& waitScope = client.getWaitScope();
if(strcmp(argv[1], "trigger") == 0) { if(strcmp(argv[1], "queue") == 0) {
if(argc < 3) { if(argc < 3) {
fprintf(stderr, "Usage %s trigger <jobName>\n", argv[0]); fprintf(stderr, "Usage %s queue <jobName>\n", argv[0]);
return EINVAL; return EINVAL;
} }
kj::Vector<capnp::RemotePromise<LaminarCi::TriggerResults>> promises; kj::Vector<capnp::RemotePromise<LaminarCi::QueueResults>> promises;
int jobNameIndex = 2; int jobNameIndex = 2;
// make a request for each job specified on the commandline // make a request for each job specified on the commandline
do { do {
auto req = laminar.triggerRequest(); auto req = laminar.queueRequest();
req.setJobName(argv[jobNameIndex]); req.setJobName(argv[jobNameIndex]);
int n = setParams(argc - jobNameIndex - 1, &argv[jobNameIndex + 1], req); int n = setParams(argc - jobNameIndex - 1, &argv[jobNameIndex + 1], req);
promises.add(req.send()); promises.add(req.send());
@ -106,14 +106,39 @@ int main(int argc, char** argv) {
return ENOENT; 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 queue <jobName>\n", argv[0]);
return EINVAL;
}
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) { if(argc < 3) {
fprintf(stderr, "Usage %s run <jobName>\n", argv[0]); fprintf(stderr, "Usage %s run <jobName>\n", argv[0]);
return EINVAL; return EINVAL;
} }
if(strcmp(argv[1], "start") == 0) {
fprintf(stderr, "Warning: \"start\" is deprecated, please use \"run\" instead\n");
}
struct: public kj::TaskSet::ErrorHandler { struct: public kj::TaskSet::ErrorHandler {
void taskFailed(kj::Exception&&) override {} void taskFailed(kj::Exception&&) override {}
} ignoreFailed; } ignoreFailed;

View File

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

View File

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

View File

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

View File

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

View File

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