]> git.pld-linux.org Git - projects/pld-builder.new.git/blob - PLD_Builder/rpm_builder.py
8dd949f8e398b6e131583999d85c9e35bf52338d
[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 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             errno = 0
110             if isinstance(error.args[0], IOError):
111                 errno = error.args[0].errno
112
113             if errno in [-3, 60, 61, 110, 111]:
114                 b.log_line("unable to connect to %s... trying again" % (src_url))
115                 continue
116             else:
117                 try:
118                     print "error.errno: %s" % str(error.errno)
119                 except Exception, e:
120                     print "error.errno: exception %s" % e
121                 try:
122                     print "error.reason %s" % str(error.reason)
123                 except Exception, e:
124                     print "error.reason exception %s" % e
125                 raise
126
127     o = chroot.popen("cat > %s" % b.src_rpm, mode = "w")
128
129     try:
130         bytes = util.sendfile(f, o)
131     except IOError, e:
132         b.log_line("error: unable to write to `%s': %s" % (b.src_rpm, e))
133         raise
134
135     f.close()
136     o.close()
137     t = time.time() - start
138     if t == 0:
139         b.log_line("fetched %d bytes" % bytes)
140     else:
141         b.log_line("fetched %d bytes, %.1f K/s" % (bytes, bytes / 1024.0 / t))
142
143 def prepare_env(logfile = None):
144     chroot.run("""
145         test ! -f /proc/uptime && mount /proc 2>/dev/null
146         test ! -c /dev/full && rm -f /dev/full && mknod -m 666 /dev/full c 1 7
147         test ! -c /dev/null && rm -f /dev/null && mknod -m 666 /dev/null c 1 3
148         test ! -c /dev/random && rm -f /dev/random && mknod -m 644 /dev/random c 1 8
149         test ! -c /dev/urandom && rm -f /dev/urandom && mknod -m 644 /dev/urandom c 1 9
150         test ! -c /dev/zero && rm -f /dev/zero && mknod -m 666 /dev/zero c 1 5
151
152         # need entry for "/" in mtab, for diskspace() to work in rpm
153         [ -z $(awk '$2 == "/" {print $1; exit}' /etc/mtab) ] && mount -f -t rootfs rootfs /
154
155         # make neccessary files readable for builder user
156         # TODO: see if they really aren't readable for builder
157         for db in Packages Name Basenames Providename Pubkeys; do
158             db=/var/lib/rpm/$db
159             test -f $db && chmod a+r $db
160         done
161
162         # try to limit network access for builder account
163         /bin/setfacl -m u:builder:--- /etc/resolv.conf
164     """, 'root', logfile = logfile)
165
166 def build_rpm(r, b):
167     packagename = b.get_package_name()
168     if not packagename:
169         # should not really get here
170         b.log_line("error: No .spec not given of malformed: '%s'" % b.spec)
171         res = "FAIL_INTERNAL"
172         return res
173
174     status.push("building %s (%s)" % (b.spec, packagename))
175     b.log_line("request from: %s" % r.requester)
176
177     if check_skip_build(r, b):
178         b.log_line("build skipped due to src builder request")
179         res = "SKIP_REQUESTED"
180         return res
181
182     b.log_line("started at: %s" % time.asctime())
183     fetch_src(r, b)
184     b.log_line("installing srpm: %s" % b.src_rpm)
185     res = chroot.run("""
186         set -ex;
187         install -d %(topdir)s/{BUILD,RPMS};
188         rpm -Uhv --nodeps %(rpmdefs)s %(src_rpm)s;
189         rm -f %(src_rpm)s;
190     """ % {
191         'topdir' : b._topdir,
192         'rpmdefs' : b.rpmbuild_opts(),
193         'src_rpm' : b.src_rpm
194     }, logfile = b.logfile)
195     b.files = []
196
197     tmpdir = b.tmpdir()
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("set -x; install -m 700 -d %s" % tmpdir, logfile=b.logfile)
204         b.default_target(config.arch)
205         # check for build arch before filling BR
206         cmd = "set -ex; TMPDIR=%(tmpdir)s exec nice -n %(nice)s " \
207             "rpmbuild -bp --short-circuit --nodeps %(rpmdefs)s --define 'prep exit 0' %(topdir)s/%(spec)s" % {
208             'tmpdir': tmpdir,
209             'nice' : config.nice,
210             'topdir' : b._topdir,
211             'rpmdefs' : b.rpmbuild_opts(),
212             'spec': b.spec,
213         }
214         res = chroot.run(cmd, logfile = b.logfile)
215         if res:
216             res = "UNSUPP"
217             b.log_line("error: build arch check (%s) failed" % cmd)
218
219         if not res:
220             if ("no-install-br" not in r.flags) and not install.uninstall_self_conflict(b):
221                 res = "FAIL_DEPS_UNINSTALL"
222             if ("no-install-br" not in r.flags) and not install.install_br(r, b):
223                 res = "FAIL_DEPS_INSTALL"
224             if not res:
225                 max_jobs = max(min(int(os.sysconf('SC_NPROCESSORS_ONLN') + 1), config.max_jobs), 1)
226                 if r.max_jobs > 0:
227                     max_jobs = max(min(config.max_jobs, r.max_jobs), 1)
228                 cmd = "set -ex; : build-id: %(r_id)s; TMPDIR=%(tmpdir)s exec nice -n %(nice)s " \
229                     "rpmbuild -bb --define '_smp_mflags -j%(max_jobs)d' --define '_pld_builder 1' %(rpmdefs)s %(topdir)s/%(spec)s" % {
230                     'r_id' : r.id,
231                     'tmpdir': tmpdir,
232                     'nice' : config.nice,
233                     'rpmdefs' : b.rpmbuild_opts(),
234                     'topdir' : b._topdir,
235                     'max_jobs' : max_jobs,
236                     'spec': b.spec,
237                 }
238                 b.log_line("building RPM using: %s" % cmd)
239                 begin_time = time.time()
240                 res = chroot.run(cmd, logfile = b.logfile)
241                 end_time = time.time()
242                 b.log_line("ended at: %s, done in %s" % (time.asctime(), datetime.timedelta(0, end_time - begin_time)))
243                 if res:
244                     res = "FAIL"
245                 files = util.collect_files(b.logfile, basedir = b._topdir)
246                 if len(files) > 0:
247                     r.chroot_files.extend(files)
248                 else:
249                     b.log_line("error: No files produced.")
250                     last_section = util.find_last_section(b.logfile)
251                     if last_section == None:
252                         res = "FAIL"
253                     else:
254                         res = "FAIL_%s" % last_section.upper()
255                 b.files = files
256
257     # cleanup tmp and build files
258     chroot.run("""
259         set -ex;
260         chmod -R u+rwX %(topdir)s/BUILD;
261         rm -rf %(topdir)s/{tmp,BUILD}
262     """ % {
263         'topdir' : b._topdir,
264     }, logfile = b.logfile)
265
266     def ll(l):
267         util.append_to(b.logfile, l)
268
269     if b.files != []:
270         rpm_cache_dir = config.rpm_cache_dir
271         if "test-build" not in r.flags:
272             # NOTE: copying to cache dir doesn't mean that build failed, so ignore result
273             b.log_line("copy rpm files to cache_dir: %s" % rpm_cache_dir)
274             chroot.run(
275                     "cp -f %s %s && poldek --mo=nodiff --mkidxz -s %s/" % \
276                         (string.join(b.files), rpm_cache_dir, rpm_cache_dir),
277                      logfile = b.logfile, user = "root"
278             )
279         else:
280             ll("test-build: not copying to " + rpm_cache_dir)
281         ll("Begin-PLD-Builder-Info")
282         if "upgrade" in r.flags:
283             b.upgraded = install.upgrade_from_batch(r, b)
284         else:
285             ll("not upgrading")
286         ll("End-PLD-Builder-Info")
287
288     for f in b.files:
289         local = r.tmp_dir + os.path.basename(f)
290         chroot.cp(f, outfile = local, rm = True)
291         ftp.add(local)
292
293     # cleanup all remains from this build
294     chroot.run("""
295         set -ex;
296         rm -rf %(topdir)s;
297     """ % {
298         'topdir' : b._topdir,
299     }, logfile = b.logfile)
300
301     def uploadinfo(b):
302         c="file:SRPMS:%s\n" % b.src_rpm
303         for f in b.files:
304             c=c + "file:ARCH:%s\n" % os.path.basename(f)
305         c=c + "END\n"
306         return c
307
308     if config.gen_upinfo and b.files != [] and 'test-build' not in r.flags:
309         fname = r.tmp_dir + b.src_rpm + ".uploadinfo"
310         f = open(fname, "w")
311         f.write(uploadinfo(b))
312         f.close()
313         ftp.add(fname, "uploadinfo")
314
315     status.pop()
316
317     return res
318
319 def handle_request(r):
320     ftp.init(r)
321     buildlogs.init(r)
322     build.build_all(r, build_rpm)
323     report.send_report(r, is_src = False)
324     ftp.flush()
325     notify.send(r)
326
327 def check_load():
328     do_exit = 0
329     try:
330         f = open("/proc/loadavg")
331         if float(string.split(f.readline())[2]) > config.max_load:
332             do_exit = 1
333     except:
334         pass
335     if do_exit:
336         sys.exit(0)
337
338 def main_for(builder):
339     msg = ""
340
341     init_conf(builder)
342
343     q = B_Queue(path.queue_file + "-" + config.builder)
344     q.lock(0)
345     q.read()
346     if q.requests == []:
347         q.unlock()
348         return
349     req = pick_request(q)
350     q.unlock()
351
352     # high priority tasks have priority < 0, normal tasks >= 0
353     if req.priority >= 0:
354
355         # allow only one build in given builder at once
356         if not lock.lock("building-rpm-for-%s" % config.builder, non_block = 1):
357             return
358         # don't kill server
359         check_load()
360         # not more then job_slots builds at once
361         locked = 0
362         for slot in range(config.job_slots):
363             if lock.lock("building-rpm-slot-%d" % slot, non_block = 1):
364                 locked = 1
365                 break
366         if not locked:
367             return
368
369         # record fact that we got lock for this builder, load balancer
370         # will use it for fair-queuing
371         l = lock.lock("got-lock")
372         f = open(path.got_lock_file, "a")
373         f.write(config.builder + "\n")
374         f.close()
375         l.close()
376     else:
377         # be able to avoid locking with very low priority
378         if req.priority > -1000:
379             # don't kill server
380             check_load()
381             # allow only one build in given builder at once
382             if not lock.lock("building-high-priority-rpm-for-%s" % config.builder, non_block = 1):
383                 return
384
385         msg = "HIGH PRIORITY: "
386
387     msg += "handling request %s (%d) for %s from %s, priority %s" \
388             % (req.id, req.no, config.builder, req.requester, req.priority)
389     log.notice(msg)
390     status.push(msg)
391     handle_request(req)
392     status.pop()
393
394     def otherreqs(r):
395         if r.no==req.no:
396             return False
397         else:
398             return True
399
400     q = B_Queue(path.queue_file + "-" + config.builder)
401     q.lock(0)
402     q.read()
403     previouslen=len(q.requests)
404     q.requests=filter(otherreqs, q.requests)
405     if len(q.requests)<previouslen:
406         q.write()
407     q.unlock()
408
409 def main():
410     if len(sys.argv) < 2:
411         raise Exception, "fatal: need to have builder name as first arg"
412     return main_for(sys.argv[1])
413
414 if __name__ == '__main__':
415     loop.run_loop(main)
This page took 0.095762 seconds and 2 git commands to generate.