mirror of
				https://github.com/ohwgiles/laminar.git
				synced 2025-06-13 12:54:29 +00:00 
			
		
		
		
	resolves #36: queue/start/run
This commit is contained in:
		
							parent
							
								
									828b66682d
								
							
						
					
					
						commit
						f1e4d10be3
					
				@ -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.
 | 
				
			||||||
 | 
				
			|||||||
@ -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;
 | 
				
			||||||
 | 
				
			|||||||
@ -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;
 | 
				
			||||||
 | 
				
			|||||||
@ -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); };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -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;
 | 
				
			||||||
 | 
				
			|||||||
@ -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);
 | 
				
			||||||
 | 
				
			|||||||
@ -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());
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user