mirror of
https://github.com/ohwgiles/laminar.git
synced 2026-10-17 08:40:54 +00:00
Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9521ba0bab | ||
|
|
82b39c2961 | ||
|
|
1eb6a9138e | ||
|
|
e7fe117426 | ||
|
|
99fd47de68 | ||
|
|
f181c87d3b | ||
|
|
87ebf91f1e | ||
|
|
10d7ad8960 | ||
|
|
a83cc6c7a5 | ||
|
|
7b010fdaf6 | ||
|
|
645ed81aea | ||
|
|
e40fb52554 | ||
|
|
1273b36260 |
@@ -84,7 +84,7 @@ generate_compressed_bins(${CMAKE_BINARY_DIR} js/angular.min.js js/angular-route.
|
|||||||
add_executable(laminard src/database.cpp src/main.cpp src/server.cpp src/laminar.cpp
|
add_executable(laminard src/database.cpp src/main.cpp src/server.cpp src/laminar.cpp
|
||||||
src/conf.cpp src/resources.cpp src/run.cpp laminar.capnp.c++ ${COMPRESSED_BINS})
|
src/conf.cpp src/resources.cpp src/run.cpp laminar.capnp.c++ ${COMPRESSED_BINS})
|
||||||
# TODO: some alternative to boost::filesystem?
|
# TODO: some alternative to boost::filesystem?
|
||||||
target_link_libraries(laminard capnp-rpc capnp kj-async kj pthread boost_filesystem boost_system sqlite3)
|
target_link_libraries(laminard capnp-rpc capnp kj-async kj pthread boost_filesystem boost_system sqlite3 z)
|
||||||
|
|
||||||
## Client
|
## Client
|
||||||
add_executable(laminarc src/client.cpp laminar.capnp.c++)
|
add_executable(laminarc src/client.cpp laminar.capnp.c++)
|
||||||
|
|||||||
80
docker-build-centos.sh
Executable file
80
docker-build-centos.sh
Executable file
@@ -0,0 +1,80 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
VERSION=0.2
|
||||||
|
|
||||||
|
OUTPUT_DIR=$PWD
|
||||||
|
|
||||||
|
SOURCE_DIR=$(readlink -f $(dirname ${BASH_SOURCE[0]}))
|
||||||
|
|
||||||
|
docker run --rm -i -v $SOURCE_DIR:/root/rpmbuild/SOURCES/laminar-$VERSION:ro -v $OUTPUT_DIR:/output centos bash -xe <<EOS
|
||||||
|
|
||||||
|
yum -y install rpm-build cmake make gcc gcc-c++ wget sqlite-devel boost-devel zlib-devel
|
||||||
|
|
||||||
|
mkdir /build
|
||||||
|
cd /build
|
||||||
|
|
||||||
|
wget -O capnproto.tar.gz https://github.com/sandstorm-io/capnproto/archive/v0.5.3.tar.gz
|
||||||
|
wget -O websocketpp.tar.gz https://github.com/zaphoyd/websocketpp/archive/0.6.0.tar.gz
|
||||||
|
wget -O rapidjson.tar.gz https://github.com/miloyip/rapidjson/archive/v1.0.2.tar.gz
|
||||||
|
md5sum -c <<EOF
|
||||||
|
909bd13ad6b8bc840ac78ab8f5bcb0a4 capnproto.tar.gz
|
||||||
|
5a485884c01f881aafbf1e055d851b82 websocketpp.tar.gz
|
||||||
|
97cc60d01282a968474c97f60714828c rapidjson.tar.gz
|
||||||
|
EOF
|
||||||
|
|
||||||
|
tar xzf capnproto.tar.gz
|
||||||
|
tar xzf websocketpp.tar.gz
|
||||||
|
tar xzf rapidjson.tar.gz
|
||||||
|
|
||||||
|
cd /build/capnproto-0.5.3/c++/
|
||||||
|
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=off .
|
||||||
|
make -j4
|
||||||
|
make install
|
||||||
|
|
||||||
|
cd /build/websocketpp-0.6.0/
|
||||||
|
cmake .
|
||||||
|
make install
|
||||||
|
|
||||||
|
cd /build/rapidjson-1.0.2/
|
||||||
|
cmake -DRAPIDJSON_BUILD_EXAMPLES=off .
|
||||||
|
make install
|
||||||
|
|
||||||
|
cd
|
||||||
|
cat <<EOF > laminar.spec
|
||||||
|
Summary: Lightweight Continuous Integration Service
|
||||||
|
Name: laminar
|
||||||
|
Version: $VERSION
|
||||||
|
Release: 1
|
||||||
|
License: GPL
|
||||||
|
BuildRequires: systemd-units
|
||||||
|
Requires: boost-filesystem zlib
|
||||||
|
|
||||||
|
%description
|
||||||
|
Lightweight Continuous Integration Service
|
||||||
|
|
||||||
|
%prep
|
||||||
|
|
||||||
|
%build
|
||||||
|
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/ %{_sourcedir}/laminar-$VERSION
|
||||||
|
pwd
|
||||||
|
make
|
||||||
|
|
||||||
|
%install
|
||||||
|
%make_install
|
||||||
|
|
||||||
|
%files
|
||||||
|
%{_bindir}/laminarc
|
||||||
|
%{_bindir}/laminard
|
||||||
|
%{_unitdir}/laminar.service
|
||||||
|
%config(noreplace) %{_sysconfdir}/laminar.conf
|
||||||
|
|
||||||
|
%post
|
||||||
|
echo Creating laminar user with home in %{_sharedstatedir}/laminar
|
||||||
|
useradd -r -d %{_sharedstatedir}/laminar -s %{_sbindir}/nologin laminar
|
||||||
|
mkdir -p %{_sharedstatedir}/laminar/cfg/{jobs,nodes,scripts}
|
||||||
|
chown -R laminar: %{_sharedstatedir}/laminar
|
||||||
|
EOF
|
||||||
|
|
||||||
|
rpmbuild -ba laminar.spec
|
||||||
|
mv rpmbuild/RPMS/x86_64/laminar-$VERSION-1.x86_64.rpm /output/
|
||||||
|
EOS
|
||||||
71
docker-build-ubuntu.sh
Executable file
71
docker-build-ubuntu.sh
Executable file
@@ -0,0 +1,71 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
VERSION=0.2
|
||||||
|
|
||||||
|
OUTPUT_DIR=$PWD
|
||||||
|
|
||||||
|
SOURCE_DIR=$(readlink -f $(dirname ${BASH_SOURCE[0]}))
|
||||||
|
|
||||||
|
docker run --rm -i -v $SOURCE_DIR:/laminar:ro -v $OUTPUT_DIR:/output ubuntu bash -xe <<EOS
|
||||||
|
|
||||||
|
apt-get update
|
||||||
|
apt-get install -y wget cmake g++ libsqlite3-dev libboost-filesystem1.55-dev zlib1g-dev
|
||||||
|
|
||||||
|
mkdir /build
|
||||||
|
cd /build
|
||||||
|
|
||||||
|
wget -O capnproto.tar.gz https://github.com/sandstorm-io/capnproto/archive/v0.5.3.tar.gz
|
||||||
|
wget -O websocketpp.tar.gz https://github.com/zaphoyd/websocketpp/archive/0.6.0.tar.gz
|
||||||
|
wget -O rapidjson.tar.gz https://github.com/miloyip/rapidjson/archive/v1.0.2.tar.gz
|
||||||
|
md5sum -c <<EOF
|
||||||
|
909bd13ad6b8bc840ac78ab8f5bcb0a4 capnproto.tar.gz
|
||||||
|
5a485884c01f881aafbf1e055d851b82 websocketpp.tar.gz
|
||||||
|
97cc60d01282a968474c97f60714828c rapidjson.tar.gz
|
||||||
|
EOF
|
||||||
|
|
||||||
|
tar xzf capnproto.tar.gz
|
||||||
|
tar xzf websocketpp.tar.gz
|
||||||
|
tar xzf rapidjson.tar.gz
|
||||||
|
|
||||||
|
cd /build/capnproto-0.5.3/c++/
|
||||||
|
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=off .
|
||||||
|
make -j4
|
||||||
|
make install
|
||||||
|
|
||||||
|
cd /build/websocketpp-0.6.0/
|
||||||
|
cmake .
|
||||||
|
make install
|
||||||
|
|
||||||
|
cd /build/rapidjson-1.0.2/
|
||||||
|
cmake -DRAPIDJSON_BUILD_EXAMPLES=off .
|
||||||
|
make install
|
||||||
|
|
||||||
|
cd /build
|
||||||
|
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/ /laminar
|
||||||
|
make -j4
|
||||||
|
mkdir laminar
|
||||||
|
make DESTDIR=laminar install/strip
|
||||||
|
|
||||||
|
mkdir laminar/DEBIAN
|
||||||
|
cat <<EOF > laminar/DEBIAN/control
|
||||||
|
Package: laminar
|
||||||
|
Version: $VERSION
|
||||||
|
Section:
|
||||||
|
Priority: optional
|
||||||
|
Architecture: amd64
|
||||||
|
Maintainer: Oliver Giles <web ohwg net>
|
||||||
|
Depends: libboost-filesystem1.55.0, zlib1g
|
||||||
|
Description: Lightweight Continuous Integration Service
|
||||||
|
EOF
|
||||||
|
cat <<EOF > laminar/DEBIAN/postinst
|
||||||
|
#!/bin/bash
|
||||||
|
echo Creating laminar user with home in /var/lib/laminar
|
||||||
|
useradd -r -d /var/lib/laminar -s /usr/sbin/nologin laminar
|
||||||
|
mkdir -p /var/lib/laminar/cfg/{jobs,nodes,scripts}
|
||||||
|
chown -R laminar: /var/lib/laminar
|
||||||
|
EOF
|
||||||
|
chmod +x laminar/DEBIAN/postinst
|
||||||
|
|
||||||
|
dpkg-deb --build laminar
|
||||||
|
mv laminar.deb /output/laminar_$VERSION-1_amd64.deb
|
||||||
|
EOS
|
||||||
@@ -19,6 +19,7 @@
|
|||||||
#include "database.h"
|
#include "database.h"
|
||||||
|
|
||||||
#include <sqlite3.h>
|
#include <sqlite3.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
Database::Database(const char *path) {
|
Database::Database(const char *path) {
|
||||||
sqlite3_open(path, &hdl);
|
sqlite3_open(path, &hdl);
|
||||||
@@ -47,15 +48,17 @@ void Database::Statement::bindValue(int i, int e) {
|
|||||||
|
|
||||||
void Database::Statement::bindValue(int i, const char* e) {
|
void Database::Statement::bindValue(int i, const char* e) {
|
||||||
sqlite3_bind_text(stmt, i, e, -1, NULL);
|
sqlite3_bind_text(stmt, i, e, -1, NULL);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Database::Statement::bindValue(int i, std::string e) {
|
void Database::Statement::bindValue(int i, std::string e) {
|
||||||
sqlite3_bind_text(stmt, i, e.c_str(), e.length(), NULL);
|
sqlite3_bind_blob(stmt, i, e.data(), e.size(), NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<> std::string Database::Statement::fetchColumn(int col) {
|
template<> std::string Database::Statement::fetchColumn(int col) {
|
||||||
return (char*)sqlite3_column_text(stmt, col);
|
int sz = sqlite3_column_bytes(stmt, col);
|
||||||
|
std::string res(sz, '\0');
|
||||||
|
memcpy(&res[0], sqlite3_column_blob(stmt, col), sz);
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<> const char* Database::Statement::fetchColumn(int col) {
|
template<> const char* Database::Statement::fetchColumn(int col) {
|
||||||
|
|||||||
@@ -19,10 +19,11 @@
|
|||||||
#include "laminar.h"
|
#include "laminar.h"
|
||||||
#include "server.h"
|
#include "server.h"
|
||||||
#include "conf.h"
|
#include "conf.h"
|
||||||
|
#include "log.h"
|
||||||
|
|
||||||
#include <sys/wait.h>
|
#include <sys/wait.h>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <kj/debug.h>
|
#include <zlib.h>
|
||||||
|
|
||||||
#include <boost/filesystem.hpp>
|
#include <boost/filesystem.hpp>
|
||||||
namespace fs = boost::filesystem;
|
namespace fs = boost::filesystem;
|
||||||
@@ -69,7 +70,12 @@ Laminar::Laminar() {
|
|||||||
// Prepare database for first use
|
// Prepare database for first use
|
||||||
// TODO: error handling
|
// TODO: error handling
|
||||||
db->exec("CREATE TABLE IF NOT EXISTS builds("
|
db->exec("CREATE TABLE IF NOT EXISTS builds("
|
||||||
"name TEXT, number INT UNSIGNED, node TEXT, queuedAt INT, startedAt INT, completedAt INT, result INT, output TEXT, parentJob TEXT, parentBuild INT, reason TEXT)");
|
"name TEXT, number INT UNSIGNED, node TEXT, queuedAt INT, "
|
||||||
|
"startedAt INT, completedAt INT, result INT, output TEXT, "
|
||||||
|
"outputLen INT, parentJob TEXT, parentBuild INT, reason TEXT, "
|
||||||
|
"PRIMARY KEY (name, number))");
|
||||||
|
db->exec("CREATE INDEX IF NOT EXISTS idx_completion_time ON builds("
|
||||||
|
"completedAt DESC)");
|
||||||
|
|
||||||
// retrieve the last build numbers
|
// retrieve the last build numbers
|
||||||
db->stmt("SELECT name, MAX(number) FROM builds GROUP BY name")
|
db->stmt("SELECT name, MAX(number) FROM builds GROUP BY name")
|
||||||
@@ -123,10 +129,16 @@ void Laminar::sendStatus(LaminarClient* client) {
|
|||||||
if(const Run* run = activeRun(client->scope.job, client->scope.num)) {
|
if(const Run* run = activeRun(client->scope.job, client->scope.num)) {
|
||||||
client->sendMessage(run->log.c_str());
|
client->sendMessage(run->log.c_str());
|
||||||
} else { // it must be finished, fetch it from the database
|
} else { // it must be finished, fetch it from the database
|
||||||
db->stmt("SELECT output FROM builds WHERE name = ? AND number = ?")
|
db->stmt("SELECT output, outputLen FROM builds WHERE name = ? AND number = ?")
|
||||||
.bind(client->scope.job, client->scope.num)
|
.bind(client->scope.job, client->scope.num)
|
||||||
.fetch<const char*>([=](const char* log) {
|
.fetch<str,int>([=](str zipped, unsigned long sz) {
|
||||||
client->sendMessage(log);
|
std::string log(sz+1,'\0');
|
||||||
|
int res = ::uncompress((unsigned char*)&log[0], &sz,
|
||||||
|
(unsigned char*)zipped.data(), zipped.size());
|
||||||
|
if(res == Z_OK)
|
||||||
|
client->sendMessage(log);
|
||||||
|
else
|
||||||
|
LLOG(ERROR, "Failed to uncompress log", res);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@@ -151,6 +163,7 @@ void Laminar::sendStatus(LaminarClient* client) {
|
|||||||
j.set("queued", run->startedAt - run->queuedAt);
|
j.set("queued", run->startedAt - run->queuedAt);
|
||||||
j.set("started", run->startedAt);
|
j.set("started", run->startedAt);
|
||||||
j.set("reason", run->reason());
|
j.set("reason", run->reason());
|
||||||
|
j.set("result", to_string(RunState::RUNNING));
|
||||||
db->stmt("SELECT completedAt - startedAt FROM builds WHERE name = ? ORDER BY completedAt DESC LIMIT 1")
|
db->stmt("SELECT completedAt - startedAt FROM builds WHERE name = ? ORDER BY completedAt DESC LIMIT 1")
|
||||||
.bind(run->name)
|
.bind(run->name)
|
||||||
.fetch<int>([&](int etc){
|
.fetch<int>([&](int etc){
|
||||||
@@ -183,6 +196,7 @@ void Laminar::sendStatus(LaminarClient* client) {
|
|||||||
j.set("number", run->build);
|
j.set("number", run->build);
|
||||||
j.set("node", run->node->name);
|
j.set("node", run->node->name);
|
||||||
j.set("started", run->startedAt);
|
j.set("started", run->startedAt);
|
||||||
|
j.set("result", to_string(RunState::RUNNING));
|
||||||
j.set("reason", run->reason());
|
j.set("reason", run->reason());
|
||||||
j.EndObject();
|
j.EndObject();
|
||||||
}
|
}
|
||||||
@@ -397,14 +411,14 @@ bool Laminar::loadConfiguration() {
|
|||||||
|
|
||||||
std::shared_ptr<Run> Laminar::queueJob(std::string name, ParamMap params) {
|
std::shared_ptr<Run> Laminar::queueJob(std::string name, ParamMap params) {
|
||||||
if(!fs::exists(fs::path(homeDir)/"cfg"/"jobs"/name)) {
|
if(!fs::exists(fs::path(homeDir)/"cfg"/"jobs"/name)) {
|
||||||
KJ_LOG(ERROR, "Non-existent job", name);
|
LLOG(ERROR, "Non-existent job", name);
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
// attempt to create a workspace for this job if it doesn't exist
|
// attempt to create a workspace for this job if it doesn't exist
|
||||||
if(!fs::exists(fs::path(homeDir)/"run"/name/"workspace")) {
|
if(!fs::exists(fs::path(homeDir)/"run"/name/"workspace")) {
|
||||||
if(!fs::create_directories(fs::path(homeDir)/"run"/name/"workspace")) {
|
if(!fs::create_directories(fs::path(homeDir)/"run"/name/"workspace")) {
|
||||||
KJ_LOG(ERROR, "Could not create job workspace", name);
|
LLOG(ERROR, "Could not create job workspace", name);
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -421,7 +435,7 @@ std::shared_ptr<Run> Laminar::queueJob(std::string name, ParamMap params) {
|
|||||||
} else if(it->first == "=reason") {
|
} else if(it->first == "=reason") {
|
||||||
run->reasonMsg = it->second;
|
run->reasonMsg = it->second;
|
||||||
} else {
|
} else {
|
||||||
KJ_LOG(ERROR, "Unknown internal job parameter", it->first);
|
LLOG(ERROR, "Unknown internal job parameter", it->first);
|
||||||
}
|
}
|
||||||
it = params.erase(it);
|
it = params.erase(it);
|
||||||
} else
|
} else
|
||||||
@@ -479,7 +493,7 @@ void Laminar::reapAdvance() {
|
|||||||
pid_t pid = waitpid(-1, &ret, 0);
|
pid_t pid = waitpid(-1, &ret, 0);
|
||||||
// TODO: handle signalled child processes
|
// TODO: handle signalled child processes
|
||||||
if(pid > 0) {
|
if(pid > 0) {
|
||||||
KJ_LOG(INFO, "Reaping", pid);
|
LLOG(INFO, "Reaping", pid);
|
||||||
auto it = activeJobs.get<0>().find(pid);
|
auto it = activeJobs.get<0>().find(pid);
|
||||||
std::shared_ptr<Run> run = *it;
|
std::shared_ptr<Run> run = *it;
|
||||||
bool completed = true;
|
bool completed = true;
|
||||||
@@ -530,14 +544,16 @@ void Laminar::assignNewJobs() {
|
|||||||
// create a working directory (different to a workspace!)
|
// create a working directory (different to a workspace!)
|
||||||
fs::path wd = fs::path(homeDir)/"run"/run->name/std::to_string(buildNum);
|
fs::path wd = fs::path(homeDir)/"run"/run->name/std::to_string(buildNum);
|
||||||
if(!fs::create_directory(wd)) {
|
if(!fs::create_directory(wd)) {
|
||||||
KJ_LOG(ERROR, "Could not create working directory", wd.string());
|
LLOG(ERROR, "Could not create working directory", wd.string());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
run->wd = wd.string();
|
run->wd = wd.string();
|
||||||
// create an archive directory
|
// create an archive directory
|
||||||
fs::path archive = fs::path(homeDir)/"archive"/run->name/std::to_string(buildNum);
|
fs::path archive = fs::path(homeDir)/"archive"/run->name/std::to_string(buildNum);
|
||||||
if(!fs::create_directories(archive)) {
|
if(fs::is_directory(archive)) {
|
||||||
KJ_LOG(ERROR, "Could not create archive directory", archive.string());
|
LLOG(WARNING, "Archive directory already exists", archive.string());
|
||||||
|
} else if(!fs::create_directories(archive)) {
|
||||||
|
LLOG(ERROR, "Could not create archive directory", archive.string());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -587,7 +603,7 @@ void Laminar::assignNewJobs() {
|
|||||||
// update next build number
|
// update next build number
|
||||||
buildNums[run->name] = buildNum;
|
buildNums[run->name] = buildNum;
|
||||||
|
|
||||||
KJ_LOG(INFO, "Queued job to node", run->name, run->build, node.name);
|
LLOG(INFO, "Queued job to node", run->name, run->build, node.name);
|
||||||
|
|
||||||
// notify clients
|
// notify clients
|
||||||
Json j;
|
Json j;
|
||||||
@@ -616,12 +632,12 @@ void Laminar::assignNewJobs() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// setup run completion handler
|
// setup run completion handler
|
||||||
run->notifyCompletion = [this](const Run* r) { runFinished(r); };
|
run->notifyCompletion = [this](Run* r) { runFinished(r); };
|
||||||
|
|
||||||
// trigger the first step of the run
|
// trigger the first step of the run
|
||||||
if(stepRun(run)) {
|
if(stepRun(run)) {
|
||||||
// should never happen
|
// should never happen
|
||||||
KJ_LOG(INFO, "No steps for run");
|
LLOG(INFO, "No steps for run");
|
||||||
run->complete();
|
run->complete();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -638,15 +654,24 @@ void Laminar::assignNewJobs() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Laminar::runFinished(const Run * r) {
|
void Laminar::runFinished(Run * r) {
|
||||||
Node* node = r->node;
|
Node* node = r->node;
|
||||||
|
|
||||||
node->busyExecutors--;
|
node->busyExecutors--;
|
||||||
KJ_LOG(INFO, "Run completed", r->name, to_string(r->result));
|
LLOG(INFO, "Run completed", r->name, to_string(r->result));
|
||||||
time_t completedAt = time(0);
|
time_t completedAt = time(0);
|
||||||
db->stmt("INSERT INTO builds VALUES(?,?,?,?,?,?,?,?,?,?,?)")
|
|
||||||
|
// compress log
|
||||||
|
std::string zipped(r->log.size(), '\0');
|
||||||
|
size_t logsize = r->log.length();
|
||||||
|
size_t zippedSize = zipped.size();
|
||||||
|
::compress((unsigned char*)&zipped[0], &zippedSize,
|
||||||
|
(unsigned char*)&r->log[0], logsize);
|
||||||
|
zipped.resize(zippedSize);
|
||||||
|
|
||||||
|
db->stmt("INSERT INTO builds VALUES(?,?,?,?,?,?,?,?,?,?,?,?)")
|
||||||
.bind(r->name, r->build, node->name, r->queuedAt, r->startedAt, completedAt, int(r->result),
|
.bind(r->name, r->build, node->name, r->queuedAt, r->startedAt, completedAt, int(r->result),
|
||||||
r->log, r->parentName, r->parentBuild, r->reason())
|
zipped, logsize, r->parentName, r->parentBuild, r->reason())
|
||||||
.exec();
|
.exec();
|
||||||
|
|
||||||
// notify clients
|
// notify clients
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ private:
|
|||||||
void reapAdvance();
|
void reapAdvance();
|
||||||
void assignNewJobs();
|
void assignNewJobs();
|
||||||
bool stepRun(std::shared_ptr<Run> run);
|
bool stepRun(std::shared_ptr<Run> run);
|
||||||
void runFinished(const Run*);
|
void runFinished(Run*);
|
||||||
bool nodeCanQueue(const Node&, const Run&) const;
|
bool nodeCanQueue(const Node&, const Run&) const;
|
||||||
// expects that Json has started an array
|
// expects that Json has started an array
|
||||||
void populateArtifacts(Json& out, std::string job, int num) const;
|
void populateArtifacts(Json& out, std::string job, int num) const;
|
||||||
|
|||||||
37
src/log.h
Normal file
37
src/log.h
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
///
|
||||||
|
/// Copyright 2015 Oliver Giles
|
||||||
|
///
|
||||||
|
/// This file is part of Laminar
|
||||||
|
///
|
||||||
|
/// Laminar is free software: you can redistribute it and/or modify
|
||||||
|
/// it under the terms of the GNU General Public License as published by
|
||||||
|
/// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
/// (at your option) any later version.
|
||||||
|
///
|
||||||
|
/// Laminar is distributed in the hope that it will be useful,
|
||||||
|
/// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
/// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
/// GNU General Public License for more details.
|
||||||
|
///
|
||||||
|
/// You should have received a copy of the GNU General Public License
|
||||||
|
/// along with Laminar. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
///
|
||||||
|
#ifndef _LAMINAR_LOG_H_
|
||||||
|
#define _LAMINAR_LOG_H_
|
||||||
|
|
||||||
|
#include <kj/debug.h>
|
||||||
|
|
||||||
|
// Simple override to prevent full paths to source files from
|
||||||
|
// appearing in log messages. Assumes / is the path separator.
|
||||||
|
// @see kj/debug.h
|
||||||
|
|
||||||
|
#define _LBASENAME(path) strrchr(path, '/') ? strrchr(path, '/') + 1 : path
|
||||||
|
|
||||||
|
#define LLOG(severity, ...) \
|
||||||
|
if (!::kj::_::Debug::shouldLog(::kj::_::Debug::Severity::severity)) {} else \
|
||||||
|
::kj::_::Debug::log(_LBASENAME(__FILE__), __LINE__, ::kj::_::Debug::Severity::severity, \
|
||||||
|
#__VA_ARGS__, __VA_ARGS__)
|
||||||
|
|
||||||
|
|
||||||
|
#endif // _LAMINAR_LOG_H_
|
||||||
|
|
||||||
@@ -17,9 +17,9 @@
|
|||||||
/// along with Laminar. If not, see <http://www.gnu.org/licenses/>
|
/// along with Laminar. If not, see <http://www.gnu.org/licenses/>
|
||||||
///
|
///
|
||||||
#include "laminar.h"
|
#include "laminar.h"
|
||||||
|
#include "log.h"
|
||||||
|
|
||||||
#include <signal.h>
|
#include <signal.h>
|
||||||
#include <kj/debug.h>
|
|
||||||
|
|
||||||
std::function<void()> sigHandler;
|
std::function<void()> sigHandler;
|
||||||
static void __sigHandler(int) { sigHandler(); }
|
static void __sigHandler(int) { sigHandler(); }
|
||||||
@@ -34,7 +34,7 @@ int main(int argc, char** argv) {
|
|||||||
do {
|
do {
|
||||||
Laminar laminar;
|
Laminar laminar;
|
||||||
sigHandler = [&](){
|
sigHandler = [&](){
|
||||||
KJ_LOG(INFO, "Received SIGINT");
|
LLOG(INFO, "Received SIGINT");
|
||||||
laminar.stop();
|
laminar.stop();
|
||||||
};
|
};
|
||||||
signal(SIGINT, &__sigHandler);
|
signal(SIGINT, &__sigHandler);
|
||||||
@@ -43,7 +43,7 @@ int main(int argc, char** argv) {
|
|||||||
laminar.run();
|
laminar.run();
|
||||||
} while(false);
|
} while(false);
|
||||||
|
|
||||||
KJ_DBG("end of main");
|
LLOG(INFO, "end of main");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -213,20 +213,20 @@ angular.module('laminar',['ngRoute','ngSanitize'])
|
|||||||
$scope.$apply();
|
$scope.$apply();
|
||||||
|
|
||||||
var chtBt = new Chart(document.getElementById("chartBt").getContext("2d")).Bar({
|
var chtBt = new Chart(document.getElementById("chartBt").getContext("2d")).Bar({
|
||||||
labels: data.recent.map(function(e){return '#' + e.number;}),
|
labels: data.recent.map(function(e){return '#' + e.number;}).reverse(),
|
||||||
datasets: [{
|
datasets: [{
|
||||||
fillColor: "darkseagreen",
|
fillColor: "darkseagreen",
|
||||||
strokeColor: "forestgreen",
|
strokeColor: "forestgreen",
|
||||||
data: data.recent.map(function(e){return e.duration;})
|
data: data.recent.map(function(e){return e.duration;}).reverse()
|
||||||
}]
|
}]
|
||||||
},
|
},
|
||||||
{barValueSpacing: 1,barStrokeWidth: 1,barDatasetSpacing:0}
|
{barValueSpacing: 1,barStrokeWidth: 1,barDatasetSpacing:0}
|
||||||
);
|
);
|
||||||
|
|
||||||
for(var i = 0; i < data.recent.length; ++i) {
|
for(var i = 0, n = data.recent.length; i < n; ++i) {
|
||||||
if(data.recent[i].result != "success") {
|
if(data.recent[i].result != "success") {
|
||||||
chtBt.datasets[0].bars[i].fillColor = "darksalmon";
|
chtBt.datasets[0].bars[n-i-1].fillColor = "darksalmon";
|
||||||
chtBt.datasets[0].bars[i].strokeColor = "crimson";
|
chtBt.datasets[0].bars[n-i-1].strokeColor = "crimson";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
chtBt.update();
|
chtBt.update();
|
||||||
@@ -306,7 +306,10 @@ angular.module('laminar',['ngRoute','ngSanitize'])
|
|||||||
.run(function($rootScope) {
|
.run(function($rootScope) {
|
||||||
angular.extend($rootScope, {
|
angular.extend($rootScope, {
|
||||||
runIcon: function(result) {
|
runIcon: function(result) {
|
||||||
return result === "success" ? '<span style="color:forestgreen;font-family:\'Zapf Dingbats\';">✔</span>' : result === "failed" ? '<span style="color:crimson;">✘</span>' : '';
|
return result === "success" ? '<span style="color:forestgreen;font-family:\'Zapf Dingbats\';">✔</span>' : result === "failed" || result === "aborted" ? '<span style="color:crimson;">✘</span>' : '';
|
||||||
|
},
|
||||||
|
runComplete: function(run) {
|
||||||
|
return !!run && (run.result === 'aborted' || run.result === 'failed' || run.result === 'success');
|
||||||
},
|
},
|
||||||
formatDate: function(unix) {
|
formatDate: function(unix) {
|
||||||
// TODO reimplement when toLocaleDateString() accepts formatting
|
// TODO reimplement when toLocaleDateString() accepts formatting
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-5 col-md-6 col-lg-7">
|
<div class="col-sm-5 col-md-6 col-lg-7">
|
||||||
<h3 style="float:left"><img class="spin" src="/progress.png" ng-hide="job.result"><span ng-bind-html="runIcon(job.result)"></span> {{name}} #{{num}}</h3>
|
<h3 style="float:left"><img class="spin" src="/progress.png" ng-show="job.result === 'running'"><span ng-bind-html="runIcon(job.result)"></span> {{name}} #{{num}}</h3>
|
||||||
<nav class="pull-left">
|
<nav class="pull-left">
|
||||||
<ul class="pagination" style="margin:15px 20px">
|
<ul class="pagination" style="margin:15px 20px">
|
||||||
<li><a href="jobs/{{name}}/{{num-1}}">«</a></li>
|
<li ng-show="num > 1"><a href="jobs/{{name}}/{{num-1}}">«</a></li>
|
||||||
<li ng-show="latestNum > num"><a ng-href="jobs/{{name}}/{{num+1}}">»</a></li>
|
<li ng-show="latestNum > num"><a ng-href="jobs/{{name}}/{{num+1}}">»</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
@@ -13,12 +13,12 @@
|
|||||||
<dt>Reason</dt><dd>{{job.reason}}</dd>
|
<dt>Reason</dt><dd>{{job.reason}}</dd>
|
||||||
<dt>Queued for</dt><dd>{{job.queued}}s</dd>
|
<dt>Queued for</dt><dd>{{job.queued}}s</dd>
|
||||||
<dt>Started</dt><dd>{{formatDate(job.started)}}</dd>
|
<dt>Started</dt><dd>{{formatDate(job.started)}}</dd>
|
||||||
<dt ng-show="job.result">Completed</dt><dd ng-show="job.result">{{formatDate(job.completed)}}</dd>
|
<dt ng-show="runComplete(job)">Completed</dt><dd ng-show="runComplete(job)">{{formatDate(job.completed)}}</dd>
|
||||||
<dt ng-show="job.result">Duration</dt><dd ng-show="job.result">{{job.duration}}s</dd>
|
<dt ng-show="runComplete(job)">Duration</dt><dd ng-show="runComplete(job)">{{job.duration}}s</dd>
|
||||||
</dl>
|
</dl>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-7 col-md-6 col-lg-5">
|
<div class="col-sm-7 col-md-6 col-lg-5">
|
||||||
<div class="progress" ng-hide="job.result">
|
<div class="progress" ng-show="job.result == 'running'">
|
||||||
<div class="progress-bar progress-bar-{{job.overtime?'warning':'info'}} progress-bar-striped {{job.etc?'':'active'}}" style="width:{{!job.etc?'100':job.progress}}%;"></div>
|
<div class="progress-bar progress-bar-{{job.overtime?'warning':'info'}} progress-bar-striped {{job.etc?'':'active'}}" style="width:{{!job.etc?'100':job.progress}}%;"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="panel panel-default" ng-show="job.artifacts.length">
|
<div class="panel panel-default" ng-show="job.artifacts.length">
|
||||||
|
|||||||
14
src/run.cpp
14
src/run.cpp
@@ -19,9 +19,9 @@
|
|||||||
#include "run.h"
|
#include "run.h"
|
||||||
#include "node.h"
|
#include "node.h"
|
||||||
#include "conf.h"
|
#include "conf.h"
|
||||||
#include <iostream>
|
#include "log.h"
|
||||||
#include <kj/debug.h>
|
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
#include <boost/filesystem.hpp>
|
#include <boost/filesystem.hpp>
|
||||||
@@ -30,6 +30,7 @@ namespace fs = boost::filesystem;
|
|||||||
std::string to_string(const RunState& rs) {
|
std::string to_string(const RunState& rs) {
|
||||||
switch(rs) {
|
switch(rs) {
|
||||||
case RunState::PENDING: return "pending";
|
case RunState::PENDING: return "pending";
|
||||||
|
case RunState::RUNNING: return "running";
|
||||||
case RunState::ABORTED: return "aborted";
|
case RunState::ABORTED: return "aborted";
|
||||||
case RunState::FAILED: return "failed";
|
case RunState::FAILED: return "failed";
|
||||||
case RunState::SUCCESS: return "success";
|
case RunState::SUCCESS: return "success";
|
||||||
@@ -46,7 +47,7 @@ Run::Run() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Run::~Run() {
|
Run::~Run() {
|
||||||
KJ_DBG("Run destroyed");
|
LLOG(INFO, "Run destroyed");
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string Run::reason() const {
|
std::string Run::reason() const {
|
||||||
@@ -100,13 +101,14 @@ bool Run::step() {
|
|||||||
for(auto& pair : params) {
|
for(auto& pair : params) {
|
||||||
setenv(pair.first.c_str(), pair.second.c_str(), false);
|
setenv(pair.first.c_str(), pair.second.c_str(), false);
|
||||||
}
|
}
|
||||||
printf("[laminar] Executing %s\n", currentScript.c_str());
|
fprintf(stderr, "[laminar] Executing %s\n", currentScript.c_str());
|
||||||
execl(currentScript.c_str(), currentScript.c_str(), NULL);
|
execl(currentScript.c_str(), currentScript.c_str(), NULL);
|
||||||
KJ_LOG(FATAL, "execl returned", strerror(errno));
|
// cannot use LLOG because stdout/stderr are captured
|
||||||
|
fprintf(stderr, "[laminar] Failed to execute %s\n", currentScript.c_str());
|
||||||
_exit(1);
|
_exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
KJ_LOG(INFO, "Forked", currentScript, pid);
|
LLOG(INFO, "Forked", currentScript, pid);
|
||||||
close(pfd[1]);
|
close(pfd[1]);
|
||||||
fd = pfd[0];
|
fd = pfd[0];
|
||||||
this->pid = pid;
|
this->pid = pid;
|
||||||
|
|||||||
@@ -29,6 +29,7 @@
|
|||||||
enum class RunState {
|
enum class RunState {
|
||||||
UNKNOWN,
|
UNKNOWN,
|
||||||
PENDING,
|
PENDING,
|
||||||
|
RUNNING,
|
||||||
ABORTED,
|
ABORTED,
|
||||||
FAILED,
|
FAILED,
|
||||||
SUCCESS
|
SUCCESS
|
||||||
@@ -67,7 +68,7 @@ public:
|
|||||||
|
|
||||||
std::string reason() const;
|
std::string reason() const;
|
||||||
|
|
||||||
std::function<void(const Run*)> notifyCompletion;
|
std::function<void(Run*)> notifyCompletion;
|
||||||
Node* node;
|
Node* node;
|
||||||
RunState result;
|
RunState result;
|
||||||
RunState lastResult;
|
RunState lastResult;
|
||||||
|
|||||||
@@ -20,12 +20,12 @@
|
|||||||
#include "interface.h"
|
#include "interface.h"
|
||||||
#include "laminar.capnp.h"
|
#include "laminar.capnp.h"
|
||||||
#include "resources.h"
|
#include "resources.h"
|
||||||
|
#include "log.h"
|
||||||
|
|
||||||
#include <capnp/ez-rpc.h>
|
#include <capnp/ez-rpc.h>
|
||||||
#include <capnp/rpc-twoparty.h>
|
#include <capnp/rpc-twoparty.h>
|
||||||
#include <capnp/rpc.capnp.h>
|
#include <capnp/rpc.capnp.h>
|
||||||
#include <kj/async-io.h>
|
#include <kj/async-io.h>
|
||||||
#include <kj/debug.h>
|
|
||||||
#include <kj/threadlocal.h>
|
#include <kj/threadlocal.h>
|
||||||
|
|
||||||
#include <websocketpp/config/core.hpp>
|
#include <websocketpp/config/core.hpp>
|
||||||
@@ -58,9 +58,9 @@ namespace {
|
|||||||
LaminarCi::JobResult fromRunState(RunState state) {
|
LaminarCi::JobResult fromRunState(RunState state) {
|
||||||
switch(state) {
|
switch(state) {
|
||||||
case RunState::SUCCESS: return LaminarCi::JobResult::SUCCESS;
|
case RunState::SUCCESS: return LaminarCi::JobResult::SUCCESS;
|
||||||
case RunState::FAILED: return LaminarCi::JobResult::FAILED;
|
case RunState::FAILED: return LaminarCi::JobResult::FAILED;
|
||||||
|
case RunState::ABORTED: return LaminarCi::JobResult::ABORTED;
|
||||||
default:
|
default:
|
||||||
KJ_DBG("TODO log state", to_string(state));
|
|
||||||
return LaminarCi::JobResult::UNKNOWN;
|
return LaminarCi::JobResult::UNKNOWN;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -81,7 +81,7 @@ public:
|
|||||||
// Start a job, without waiting for it to finish
|
// Start a job, without waiting for it to finish
|
||||||
kj::Promise<void> trigger(TriggerContext context) override {
|
kj::Promise<void> trigger(TriggerContext context) override {
|
||||||
std::string jobName = context.getParams().getJobName();
|
std::string jobName = context.getParams().getJobName();
|
||||||
KJ_LOG(INFO, "RPC trigger", jobName);
|
LLOG(INFO, "RPC trigger", 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();
|
||||||
@@ -96,7 +96,7 @@ public:
|
|||||||
// Start a job and wait for the result
|
// Start a job and wait for the result
|
||||||
kj::Promise<void> start(StartContext context) override {
|
kj::Promise<void> start(StartContext context) override {
|
||||||
std::string jobName = context.getParams().getJobName();
|
std::string jobName = context.getParams().getJobName();
|
||||||
KJ_LOG(INFO, "RPC start", jobName);
|
LLOG(INFO, "RPC start", 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();
|
||||||
@@ -116,7 +116,7 @@ public:
|
|||||||
kj::Promise<void> pend(PendContext context) override {
|
kj::Promise<void> pend(PendContext context) override {
|
||||||
std::string jobName = context.getParams().getJobName();
|
std::string jobName = context.getParams().getJobName();
|
||||||
int buildNum = context.getParams().getBuildNum();
|
int buildNum = context.getParams().getBuildNum();
|
||||||
KJ_LOG(INFO, "RPC pend", jobName, buildNum);
|
LLOG(INFO, "RPC pend", jobName, buildNum);
|
||||||
|
|
||||||
kj::Promise<RunState> promise = laminar.waitForRun(jobName, buildNum);
|
kj::Promise<RunState> promise = laminar.waitForRun(jobName, buildNum);
|
||||||
|
|
||||||
@@ -129,7 +129,7 @@ public:
|
|||||||
kj::Promise<void> set(SetContext context) override {
|
kj::Promise<void> set(SetContext context) override {
|
||||||
std::string jobName = context.getParams().getJobName();
|
std::string jobName = context.getParams().getJobName();
|
||||||
int buildNum = context.getParams().getBuildNum();
|
int buildNum = context.getParams().getBuildNum();
|
||||||
KJ_LOG(INFO, "RPC set", jobName, buildNum);
|
LLOG(INFO, "RPC set", jobName, buildNum);
|
||||||
|
|
||||||
LaminarCi::MethodResult result = laminar.setParam(jobName, buildNum,
|
LaminarCi::MethodResult result = laminar.setParam(jobName, buildNum,
|
||||||
context.getParams().getParam().getName(), context.getParams().getParam().getValue())
|
context.getParams().getParam().getName(), context.getParams().getParam().getValue())
|
||||||
|
|||||||
Reference in New Issue
Block a user