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