diff --git a/CMakeLists.txt b/CMakeLists.txt index a41cef9..e667f26 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -82,7 +82,7 @@ generate_compressed_bins(${CMAKE_BINARY_DIR} js/angular.min.js js/angular-route. ## Server add_executable(laminard src/database.cpp src/main.cpp src/server.cpp src/laminar.cpp - src/conf.cpp src/resources.cpp src/run.cpp src/node.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? target_link_libraries(laminard capnp capnp-rpc kj-async kj boost_filesystem boost_system sqlite3) diff --git a/src/laminar.cpp b/src/laminar.cpp index 1efafd7..29d79d3 100644 --- a/src/laminar.cpp +++ b/src/laminar.cpp @@ -310,6 +310,17 @@ bool Laminar::loadConfiguration() { Node node; node.name = it.path().filename().string(); node.numExecutors = conf.get("EXECUTORS", 6); + + std::string tags = conf.get("TAGS"); + if(!tags.empty()) { + std::istringstream iss(tags); + std::set tagList; + std::copy(std::istream_iterator(iss), + std::istream_iterator(), + std::inserter(tagList, tagList.begin())); + node.tags = tagList; + } + nm.emplace(node.name, std::move(node)); } } @@ -324,6 +335,32 @@ bool Laminar::loadConfiguration() { nodes = nm; + fs::path jobsDir = fs::path(homeDir)/"cfg"/"jobs"; + if(fs::is_directory(jobsDir)) { + fs::directory_iterator dit(jobsDir); + for(fs::directory_entry& it : dit) { + if(!fs::is_directory(it.status())) + continue; + + fs::directory_entry config(it.path()/"config"); + if(!fs::is_regular_file(config.status())) + continue; + + StringMap conf = parseConfFile(config.path().string().c_str()); + + std::string tags = conf.get("TAGS"); + if(!tags.empty()) { + std::istringstream iss(tags); + std::set tagList; + std::copy(std::istream_iterator(iss), + std::istream_iterator(), + std::inserter(tagList, tagList.begin())); + jobTags[it.path().filename().string()] = tagList; + } + + } + } + return true; } @@ -425,6 +462,29 @@ void Laminar::reapAdvance() { assignNewJobs(); } +bool Laminar::nodeCanQueue(const Node& node, const Run& run) const { + // if a node is too busy, it can't take the job + if(node.busyExecutors >= node.numExecutors) + return false; + + auto it = jobTags.find(run.name); + // if both nodes have no tags, it's OK + if(it == jobTags.end() && node.tags.size() == 0) + return true; + + // but if just one of them does, don't allow the build + if(it == jobTags.end() || node.tags.size() == 0) + return false; + + // in other cases, allow the build if they have a tag in common + for(const std::string& tag : it->second) { + if(node.tags.find(tag) != node.tags.end()) + return true; + } + + return false; +} + void Laminar::assignNewJobs() { auto it = queuedJobs.begin(); while(it != queuedJobs.end()) { @@ -432,7 +492,7 @@ void Laminar::assignNewJobs() { for(auto& sn : nodes) { Node& node = sn.second; std::shared_ptr run = *it; - if(node.queue(*run)) { + if(nodeCanQueue(node, *run)) { node.busyExecutors++; run->node = &node; run->startedAt = time(0); diff --git a/src/laminar.h b/src/laminar.h index 1af5fed..c9ebebf 100644 --- a/src/laminar.h +++ b/src/laminar.h @@ -61,6 +61,7 @@ private: void assignNewJobs(); bool stepRun(std::shared_ptr run); void runFinished(const Run*); + bool nodeCanQueue(const Node&, const Run&) const; std::list> queuedJobs; @@ -79,6 +80,8 @@ private: std::unordered_map buildNums; + std::unordered_map> jobTags; + RunSet activeJobs; Database* db; Server* srv; diff --git a/src/node.cpp b/src/node.cpp deleted file mode 100644 index 4b9e1fa..0000000 --- a/src/node.cpp +++ /dev/null @@ -1,25 +0,0 @@ -/// -/// 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 -/// -#include "node.h" - -bool Node::queue(const Run& run) { - // later this could check if the given run is allowed to run - // on this node, for example if the run's tags match the node's tags - return busyExecutors < numExecutors; -} diff --git a/src/node.h b/src/node.h index a78ffc7..e414ddc 100644 --- a/src/node.h +++ b/src/node.h @@ -16,10 +16,11 @@ /// You should have received a copy of the GNU General Public License /// along with Laminar. If not, see /// -#ifndef NODE_H -#define NODE_H +#ifndef _LAMINAR_NODE_H_ +#define _LAMINAR_NODE_H_ #include +#include class Run; @@ -32,10 +33,11 @@ public: std::string name; int numExecutors; int busyExecutors = 0; + std::set tags; // Attempts to queue the given run to this node. Returns true if succeeded. bool queue(const Run& run); }; -#endif // NODE_H +#endif // _LAMINAR_NODE_H_ diff --git a/src/run.cpp b/src/run.cpp index 0bc8a4d..759c718 100644 --- a/src/run.cpp +++ b/src/run.cpp @@ -91,7 +91,8 @@ bool Run::step() { setenv("PATH", PATH.c_str(), true); setenv("lBuildNum", buildNum.c_str(), true); setenv("lJobName", name.c_str(), true); - setenv("lNode", node->name.c_str(), true); + if(!node->name.empty()) + setenv("lNode", node->name.c_str(), true); setenv("lResult", to_string(result).c_str(), true); setenv("lLastResult", to_string(lastResult).c_str(), true); setenv("lWorkspace", (fs::path(laminarHome)/"run"/name/"workspace").string().c_str(), true);