]> git.pld-linux.org Git - projects/pld-builder.new.git/blob - PLD_Builder/rpm_builder.py
timedelta should make this msg stop lying
[projects/pld-builder.new.git] / PLD_Builder / rpm_builder.py
1 # vi: encoding=utf-8 ts=8 sts=4 sw=4 et
2
3 import sys
4 import os
5 import atexit
6 import time
7 import datetime
8 import string
9 import urllib2
10
11 from config import config, init_conf
12 from bqueue import B_Queue
13 import lock
14 import util
15 import loop
16 import path
17 import status
18 import log
19 import chroot
20 import ftp
21 import buildlogs
22 import notify
23 import build
24 import report
25 import install
26
27 # *HACK*HACK*HACK*HACK*HACK*HACK*HACK*HACK*HACK*HACK*HACK*HACK*HACK*HACK*HACK*
28 import socket
29
30 socket.myorigsocket=socket.socket
31
32 def mysocket(family=socket.AF_INET, type=socket.SOCK_STREAM, proto=0):
33     s=socket.myorigsocket(family, type, proto)
34     s.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
35     return s
36
37 socket.socket=mysocket
38 # *HACK*HACK*HACK*HACK*HACK*HACK*HACK*HACK*HACK*HACK*HACK*HACK*HACK*HACK*HACK*
39
40 # this code is duplicated in srpm_builder, but we
41 # might want to handle some cases differently here
42 def pick_request(q):
43     def mycmp(r1, r2):
44         if r1.kind != 'group' or r2.kind != 'group':
45             raise Exception, "non-group requests"
46         pri_diff = cmp(r1.priority, r2.priority)
47         if pri_diff == 0:
48             return cmp(r1.time, r2.time)
49         else:
50             return pri_diff
51     q.requests.sort(mycmp)
52     ret = q.requests[0]
53     return ret
54
55 def check_skip_build(r, b):
56     src_url = config.control_url + "/srpms/" + r.id + "/skipme"
57     good  = False
58     b.log_line("checking if we should skip the build")
59     while not good:
60         try:
61             headers = { 'Cache-Control': 'no-cache', 'Pragma': 'no-cache' }
62             req = urllib2.Request(url=src_url, headers=headers)
63             f = urllib2.urlopen(req)
64             good = True
65         except urllib2.HTTPError, error:
66                 return False
67         except urllib2.URLError, error:
68             # see errno.h
69             if error.errno in [-3, 60, 61, 110, 111]:
70                 b.log_line("unable to connect... trying again")
71                 continue
72             else:
73                 return False
74         f.close()
75         return True
76     return False
77
78 def fetch_src(r, b):
79     src_url = config.control_url + "/srpms/" + r.id + "/" + b.src_rpm
80     b.log_line("fetching %s" % src_url)
81     start = time.time()
82     good = False
83     while not good:
84         try:
85             headers = { 'Cache-Control': 'no-cache', 'Pragma': 'no-cache' }
86             req = urllib2.Request(url=src_url, headers=headers)
87             f = urllib2.urlopen(req)
88             good = True
89         except urllib2.HTTPError, error:
90             # fail in a way where cron job will retry
91             msg = "unable to fetch file, http code: %d" % error.code
92             b.log_line(msg)
93             queue_time = time.time() - r.time
94             # 6 hours
95             if error.code != 404 or (queue_time >= 0 and queue_time < (6 * 60 * 60)):
96                 raise IOError, msg
97             else:
98                 msg = "in queue for more than 6 hours, download failing"
99                 b.log_line(msg)
100                 return False
101         except urllib2.URLError, error:
102             # see errno.h
103             if error.errno in [-3, 60, 61, 110, 111]:
104                 b.log_line("unable to connect... trying again")
105                 continue
106             else:
107                 raise
108
109     o = chroot.popen("cat > %s" % b.src_rpm, mode = "w")
110
111     try:
112         bytes = util.sendfile(f, o)
113     except IOError, e:
114         b.log_line("error: unable to write to `%s': %s" % (b.src_rpm, e))
115         raise
116
117     f.close()
118     o.close()
119     t = time.time() - start
120     if t == 0:
121         b.log_line("fetched %d bytes" % bytes)
122     else:
123         b.log_line("fetched %d bytes, %.1f K/s" % (bytes, bytes / 1024.0 / t))
124
125 def prepare_env():
126     chroot.run("""
127         test ! -f /proc/uptime && mount /proc 2>/dev/null
128         test ! -c /dev/full && rm -f /dev/full && mknod -m 666 /dev/full c 1 7
129         test ! -c /dev/null && rm -f /dev/null && mknod -m 666 /dev/null c 1 3
130         test ! -c /dev/random && rm -f /dev/random && mknod -m 644 /dev/random c 1 8
131         test ! -c /dev/urandom && rm -f /dev/urandom && mknod -m 644 /dev/urandom c 1 9
132         test ! -c /dev/zero && rm -f /dev/zero && mknod -m 666 /dev/zero c 1 5
133
134         # make neccessary files readable for builder user
135         # TODO: see if they really aren't readable for builder
136         for db in Packages Name Basenames Providename Pubkeys; do
137             db=/var/lib/rpm/$db
138             test -f $db && chmod a+r $db
139         done
140
141         # try to limit network access for builder account
142         /bin/setfacl -m u:builder:--- /etc/resolv.conf
143     """, 'root')
144
145 def build_rpm(r, b):
146     status.push("building %s" % b.spec)
147     b.log_line("request from: %s" % r.requester)
148
149     if check_skip_build(r, b):
150         b.log_line("build skipped due to src builder request")
151         res = "SKIP_REQUESTED"
152         return res
153
154     b.log_line("started at: %s" % time.asctime())
155     fetch_src(r, b)
156     b.log_line("installing srpm: %s" % b.src_rpm)
157     res = chroot.run("""
158         install -d rpm/SPECS rpm/SOURCES
159         rpm -Uhv %s
160     """ % b.src_rpm, logfile = b.logfile)
161     chroot.run("rm -f %s" % b.src_rpm, logfile = b.logfile)
162     b.files = []
163     tmpdir = "/tmp/B." + b.b_id[0:6]
164     if res:
165         b.log_line("error: installing src rpm failed")
166         res = "FAIL_SRPM_INSTALL"
167     else:
168         prepare_env()
169         chroot.run("install -m 700 -d %s" % tmpdir)
170
171         b.default_target(config.arch)
172         rpmbuild_opt = "%s %s %s" % (b.target_string(), b.kernel_string(), b.bconds_string())
173         # check for build arch before filling BR
174         cmd = "cd rpm/SPECS; TMPDIR=%s exec nice -n %s rpmbuild -bp --short-circuit --nodeps --define 'prep exit 0' %s %s" % \
175             (tmpdir, config.nice, rpmbuild_opt, b.spec)
176         res = chroot.run(cmd, logfile = b.logfile)
177         if res:
178             res = "UNSUPP"
179             b.log_line("error: build arch check (%s) failed" % cmd)
180
181         if not res:
182             if ("no-install-br" not in r.flags) and not install.uninstall_self_conflict(b):
183                 res = "FAIL_DEPS_UNINSTALL"
184             if ("no-install-br" not in r.flags) and not install.install_br(r, b):
185                 res = "FAIL_DEPS_INSTALL"
186             if not res:
187                 max_jobs = max(min(int(os.sysconf('SC_NPROCESSORS_ONLN') + 1), config.max_jobs), 1)
188                 if r.max_jobs > 0:
189                     max_jobs = max(min(config.max_jobs, r.max_jobs), 1)
190                 cmd = "echo build-id: %s; cd rpm/SPECS; TMPDIR=%s exec nice -n %s rpmbuild -bb --define '_smp_mflags -j%d' %s %s" % \
191                             (r.id, tmpdir, config.nice, max_jobs, rpmbuild_opt, b.spec)
192                 b.log_line("building RPM using: %s" % cmd)
193                 begin_time = time.time()
194                 res = chroot.run(cmd, logfile = b.logfile)
195                 end_time = time.time()
196                 b.log_line("ended at: %s, done in %s" % (time.asctime(), datetime.timedelta(0, end_time - begin_time)))
197                 if res:
198                     res = "FAIL"
199                 files = util.collect_files(b.logfile)
200                 if len(files) > 0:
201                     r.chroot_files.extend(files)
202                 else:
203                     b.log_line("error: No files produced.")
204                     last_section = util.find_last_section(b.logfile)
205                     if last_section == None:
206                         res = "FAIL"
207                     else:
208                         res = "FAIL_%s" % last_section.upper()
209                 b.files = files
210
211     chroot.run("rm -rf %s; cd rpm/SPECS; rpmbuild --nodeps --nobuild " \
212                          "--clean --rmspec --rmsource %s" % \
213                          (tmpdir, b.spec), logfile = b.logfile)
214     chroot.run("chmod -R u+rwX rpm/BUILD/*; rm -rf rpm/BUILD/*", logfile = b.logfile)
215
216     def ll(l):
217         util.append_to(b.logfile, l)
218  
219     if b.files != []:
220         rpm_cache_dir = config.rpm_cache_dir
221         if "test-build" not in r.flags:
222             # NOTE: copying to cache dir doesn't mean that build failed, so ignore result
223             b.log_line("copy rpm files to cache_dir: %s" % rpm_cache_dir)
224             chroot.run(
225                     "cp -f %s %s && poldek --mo=nodiff --mkidxz -s %s/" % \
226                         (string.join(b.files), rpm_cache_dir, rpm_cache_dir),
227                      logfile = b.logfile, user = "root"
228             )
229         else:
230             ll("test-build: not copying to " + rpm_cache_dir)
231         ll("Begin-PLD-Builder-Info")
232         if "upgrade" in r.flags:
233             b.upgraded = install.upgrade_from_batch(r, b)
234         else:
235             ll("not upgrading")
236         ll("End-PLD-Builder-Info")
237
238     for f in b.files:
239         local = r.tmp_dir + os.path.basename(f)
240         chroot.cp(f, outfile = local, rm = True)
241         ftp.add(local)
242
243     def uploadinfo(b):
244         c="file:SRPMS:%s\n" % b.src_rpm
245         for f in b.files:
246             c=c + "file:ARCH:%s\n" % os.path.basename(f)
247         c=c + "END\n"
248         return c
249
250     if config.gen_upinfo and b.files != [] and 'test-build' not in r.flags:
251         fname = r.tmp_dir + b.src_rpm + ".uploadinfo"
252         f = open(fname, "w")
253         f.write(uploadinfo(b))
254         f.close()
255         ftp.add(fname, "uploadinfo")
256
257     status.pop()
258
259     return res
260
261 def handle_request(r):
262     ftp.init(r)
263     buildlogs.init(r)
264     build.build_all(r, build_rpm)
265     report.send_report(r, is_src = False)
266     ftp.flush()
267     notify.send(r)
268
269 def check_load():
270     do_exit = 0
271     try:
272         f = open("/proc/loadavg")
273         if float(string.split(f.readline())[2]) > config.max_load:
274             do_exit = 1
275     except:
276         pass
277     if do_exit:
278         sys.exit(0)
279
280 def main_for(builder):
281     msg = ""
282
283     init_conf(builder)
284
285     q = B_Queue(path.queue_file + "-" + config.builder)
286     q.lock(0)
287     q.read()
288     if q.requests == []:
289         q.unlock()
290         return
291     req = pick_request(q)
292     q.unlock()
293
294     # high priority tasks have priority < 0, normal tasks >= 0
295     if req.priority >= 0:
296
297         # allow only one build in given builder at once
298         if not lock.lock("building-rpm-for-%s" % config.builder, non_block = 1):
299             return
300         # don't kill server
301         check_load()
302         # not more then job_slots builds at once
303         locked = 0
304         for slot in range(config.job_slots):
305             if lock.lock("building-rpm-slot-%d" % slot, non_block = 1):
306                 locked = 1
307                 break
308         if not locked:
309             return
310
311         # record fact that we got lock for this builder, load balancer
312         # will use it for fair-queuing
313         l = lock.lock("got-lock")
314         f = open(path.got_lock_file, "a")
315         f.write(config.builder + "\n")
316         f.close()
317         l.close()
318     else:
319         msg = "HIGH PRIORITY: "
320     
321     msg += "handling request %s (%d) for %s from %s, priority %s" \
322             % (req.id, req.no, config.builder, req.requester, req.priority)
323     log.notice(msg)
324     status.push(msg)
325     handle_request(req)
326     status.pop()
327
328     def otherreqs(r):
329         if r.no==req.no:
330             return False
331         else:
332             return True
333
334     q = B_Queue(path.queue_file + "-" + config.builder)
335     q.lock(0)
336     q.read()
337     previouslen=len(q.requests)
338     q.requests=filter(otherreqs, q.requests)
339     if len(q.requests)<previouslen:
340         q.write()
341     q.unlock()
342     
343 def main():
344     if len(sys.argv) < 2:
345         raise Exception, "fatal: need to have builder name as first arg"
346     return main_for(sys.argv[1])
347     
348 if __name__ == '__main__':
349     loop.run_loop(main)
This page took 0.059595 seconds and 4 git commands to generate.