Add sql caching buildfarm.
[build-farm.git] / buildfarm / __init__.py
1 #!/usr/bin/python
2 # Simple database query script for the buildfarm
3 #
4 # Copyright (C) Jelmer Vernooij <jelmer@samba.org>         2010
5 #
6 #   This program is free software; you can redistribute it and/or modify
7 #   it under the terms of the GNU General Public License as published by
8 #   the Free Software Foundation; either version 2 of the License, or
9 #   (at your option) any later version.
10 #
11 #   This program is distributed in the hope that it will be useful,
12 #   but WITHOUT ANY WARRANTY; without even the implied warranty of
13 #   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 #   GNU General Public License for more details.
15 #
16 #   You should have received a copy of the GNU General Public License
17 #   along with this program; if not, write to the Free Software
18 #   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19
20 import ConfigParser
21 import os
22 import re
23 import sqlite3
24
25
26 class Tree(object):
27     """A tree to build."""
28
29     def __init__(self, name, scm, repo, branch, subdir="", srcdir=""):
30         self.name = name
31         self.repo = repo
32         self.scm = scm
33         self.branch = branch
34         self.subdir = subdir
35         self.srcdir = srcdir
36         self.scm = scm
37
38     def get_branch(self):
39         from buildfarm.history import GitBranch
40         return GitBranch(self.repo, self.branch)
41
42     def __repr__(self):
43         return "<%s %r>" % (self.__class__.__name__, self.name)
44
45
46 def read_trees_from_conf(path):
47     """Read trees from a configuration file.
48
49     :param path: tree path
50     :return: Dictionary with trees
51     """
52     ret = {}
53     cfp = ConfigParser.ConfigParser()
54     cfp.read(path)
55     for s in cfp.sections():
56         ret[s] = Tree(name=s, **dict(cfp.items(s)))
57     return ret
58
59
60 def lcov_extract_percentage(text):
61     """Extract the coverage percentage from the lcov file."""
62     m = re.search('\<td class="headerItem".*?\>Code\&nbsp\;covered\:\<\/td\>.*?\n.*?\<td class="headerValue".*?\>([0-9.]+) \%', text)
63     if m:
64         return m.group(1)
65     else:
66         return None
67
68
69 class BuildFarm(object):
70
71     LCOVHOST = "magni"
72     OLDAGE = 60*60*4,
73     DEADAGE = 60*60*24*4
74
75     def __init__(self, path=None):
76         if path is None:
77             path = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
78         self.path = path
79         self.webdir = os.path.join(self.path, "web")
80         if not os.path.isdir(path):
81             raise Exception("web directory %s does not exist" % self.webdir)
82         self.trees = read_trees_from_conf(os.path.join(self.webdir, "trees.conf"))
83         self.builds = self._open_build_results()
84         self.upload_builds = self._open_upload_build_results()
85         self.hostdb = self._open_hostdb()
86         self.compilers = self._load_compilers()
87         self.lcovdir = os.path.join(self.path, "lcov/data")
88
89     def __repr__(self):
90         return "%s(%r)" % (self.__class__.__name__, self.path)
91
92     def _open_build_results(self):
93         from buildfarm import data
94         return data.BuildResultStore(os.path.join(self.path, "data", "oldrevs"))
95
96     def _open_upload_build_results(self):
97         from buildfarm import data
98         return data.UploadBuildResultStore(os.path.join(self.path, "data", "upload"))
99
100     def _open_hostdb(self):
101         from buildfarm import hostdb
102         return hostdb.HostDatabase(
103             os.path.join(self.path, "hostdb.sqlite"))
104
105     def _load_compilers(self):
106         from buildfarm import util
107         return set(util.load_list(os.path.join(self.webdir, "compilers.list")))
108
109     def lcov_status(self, tree):
110         """get status of build"""
111         from buildfarm import data, util
112         file = os.path.join(self.lcovdir, self.LCOVHOST, tree, "index.html")
113         try:
114             lcov_html = util.FileLoad(file)
115         except (OSError, IOError):
116             # File does not exist
117             raise data.NoSuchBuildError(tree, self.LCOVHOST, "lcov")
118
119         perc = lcov_extract_percentage(lcov_html)
120         if perc is None:
121             ret = ""
122         else:
123             ret = perc
124         return perc
125
126     def get_build(self, tree, host, compiler, rev=None):
127         if rev:
128             return self.builds.get_build(tree, host, compiler, rev)
129         else:
130             return self.upload_builds.get_build(tree, host, compiler)
131
132     def get_new_builds(self):
133         hosts = set([host.name for host in self.hostdb.hosts()])
134         for build in self.upload_builds.get_new_builds():
135             if build.tree in self.trees and build.compiler in self.compilers and build.host in hosts:
136                 yield build
137
138
139 class CachingBuildFarm(BuildFarm):
140
141     def __init__(self, path=None, readonly=False, cachedirname=None):
142         self._cachedirname = cachedirname
143         self.readonly = readonly
144         super(CachingBuildFarm, self).__init__(path)
145
146     def _get_cachedir(self):
147         if self._cachedirname is not None:
148             return os.path.join(self.path, self._cachedirname)
149         else:
150             return os.path.join(self.path, "cache")
151
152     def _open_build_results(self):
153         from buildfarm import data
154         return data.CachingBuildResultStore(os.path.join(self.path, "data", "oldrevs"),
155                 self._get_cachedir(), readonly=self.readonly)
156
157     def _open_upload_build_results(self):
158         from buildfarm import data
159         return data.CachingUploadBuildResultStore(os.path.join(self.path, "data", "upload"),
160                 self._get_cachedir(), readonly=self.readonly)
161
162     def lcov_status(self, tree):
163         """get status of build"""
164         from buildfarm import data, util
165         cachefile = os.path.join(self._get_cachedir(),
166                                     "lcov.%s.%s.status" % (self.LCOVHOST, tree))
167         file = os.path.join(self.lcovdir, self.LCOVHOST, tree, "index.html")
168         try:
169             st1 = os.stat(file)
170         except OSError:
171             # File does not exist
172             raise data.NoSuchBuildError(tree, self.LCOVHOST, "lcov")
173         try:
174             st2 = os.stat(cachefile)
175         except OSError:
176             # file does not exist
177             st2 = None
178
179         if st2 and st1.st_ctime <= st2.st_mtime:
180             ret = util.FileLoad(cachefile)
181             if ret == "":
182                 return None
183             return ret
184
185         perc = super(CachingBuildFarm, self).lcov_status(tree)
186         if not self.readonly:
187             util.FileSave(cachefile, perc)
188         return perc
189
190
191 class SQLCachingBuildFarm(BuildFarm):
192
193     def __init__(self, path=None, db=None):
194         self.db = db
195         super(SQLCachingBuildFarm, self).__init__(path)
196
197     def _get_db(self):
198         if self.db is not None:
199             return self.db
200         else:
201             return sqlite3.connect(os.path.join(self.path, "hostdb.sqlite"))
202
203     def _open_build_results(self):
204         from buildfarm import data
205         return data.SQLCachingBuildResultStore(os.path.join(self.path, "data", "oldrevs"),
206             self.db)
207
208
209 def setup_db(db):
210     db.executescript("""
211         CREATE TABLE IF NOT EXISTS host (name text, owner text, owner_email text, password text, ssh_access int, fqdn text, platform text, permission text, last_dead_mail int, join_time int);
212         CREATE UNIQUE INDEX IF NOT EXISTS unique_hostname ON host (name);
213         CREATE TABLE IF NOT EXISTS build (id integer primary key autoincrement, tree text, revision text, host text, compiler text, checksum text, age int, status text, commit_revision text);
214         CREATE UNIQUE INDEX IF NOT EXISTS unique_checksum ON build (checksum);
215         CREATE TABLE IF NOT EXISTS test_run (build int, test text, result text, output text);
216         """)