]> TLD Linux GIT Repositories - tld-builder.git/blob - TLD_Builder/rpm_builder.py
- fix rpm opts
[tld-builder.git] / TLD_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         LC_ALL=en_US.UTF-8 rpm -qp --changelog %(src_rpm)s;
189         rpm -Uhv --nodeps %(rpmdefs)s %(src_rpm)s;
190         rm -f %(src_rpm)s;
191     """ % {
192         'topdir' : b.get_topdir(),
193         'rpmdefs' : b.rpmbuild_opts(),
194         'src_rpm' : b.src_rpm
195     }, logfile = b.logfile)
196     b.files = []
197
198     tmpdir = b.tmpdir()
199     if res:
200         b.log_line("error: installing src rpm failed")
201         res = "FAIL_SRPM_INSTALL"
202     else:
203         prepare_env()
204         chroot.run("set -x; install -m 700 -d %s" % tmpdir, logfile=b.logfile)
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' %(topdir)s/%(spec)s" % {
209             'tmpdir': tmpdir,
210             'nice' : config.nice,
211             'topdir' : b.get_topdir(),
212             'rpmdefs' : b.rpmbuild_opts(),
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' --define '_tld_builder 1' %(rpmdefs)s %(topdir)s/%(spec)s" % {
231                     'r_id' : r.id,
232                     'tmpdir': tmpdir,
233                     'nice' : config.nice,
234                     'rpmdefs' : b.rpmbuild_opts(),
235                     'topdir' : b.get_topdir(),
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, basedir = b.get_topdir())
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     # cleanup tmp and build files
259     chroot.run("""
260         set -ex;
261         chmod -R u+rwX %(topdir)s/BUILD;
262         rm -rf %(topdir)s/{tmp,BUILD}
263     """ % {
264         'topdir' : b.get_topdir(),
265     }, 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-TLD-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-TLD-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     # cleanup all remains from this build
295     chroot.run("""
296         set -ex;
297         rm -rf %(topdir)s;
298     """ % {
299         'topdir' : b.get_topdir(),
300     }, logfile = b.logfile)
301
302     def uploadinfo(b):
303         c="file:SRPMS:%s\n" % b.src_rpm
304         for f in b.files:
305             c=c + "file:ARCH:%s\n" % os.path.basename(f)
306         c=c + "END\n"
307         return c
308
309     if config.gen_upinfo and b.files != [] and 'test-build' not in r.flags:
310         fname = r.tmp_dir + b.src_rpm + ".uploadinfo"
311         f = open(fname, "w")
312         f.write(uploadinfo(b))
313         f.close()
314         ftp.add(fname, "uploadinfo")
315
316     status.pop()
317
318     return res
319
320 def handle_request(r):
321     ftp.init(r)
322     buildlogs.init(r)
323     build.build_all(r, build_rpm)
324     report.send_report(r, is_src = False)
325     ftp.flush()
326     notify.send(r)
327
328 def check_load():
329     do_exit = 0
330     try:
331         f = open("/proc/loadavg")
332         if float(string.split(f.readline())[2]) > config.max_load:
333             do_exit = 1
334     except:
335         pass
336     if do_exit:
337         sys.exit(0)
338
339 def main_for(builder):
340     msg = ""
341
342     init_conf(builder)
343
344     q = B_Queue(path.queue_file + "-" + config.builder)
345     q.lock(0)
346     q.read()
347     if q.requests == []:
348         q.unlock()
349         return
350     req = pick_request(q)
351     q.unlock()
352
353     # high priority tasks have priority < 0, normal tasks >= 0
354     if req.priority >= 0:
355
356         # allow only one build in given builder at once
357         if not lock.lock("building-rpm-for-%s" % config.builder, non_block = 1):
358             return
359         # don't kill server
360         check_load()
361         # not more then job_slots builds at once
362         locked = 0
363         for slot in range(config.job_slots):
364             if lock.lock("building-rpm-slot-%d" % slot, non_block = 1):
365                 locked = 1
366                 break
367         if not locked:
368             return
369
370         # record fact that we got lock for this builder, load balancer
371         # will use it for fair-queuing
372         l = lock.lock("got-lock")
373         f = open(path.got_lock_file, "a")
374         f.write(config.builder + "\n")
375         f.close()
376         l.close()
377     else:
378         # be able to avoid locking with very low priority
379         if req.priority > -1000:
380             # don't kill server
381             check_load()
382             # allow only one build in given builder at once
383             if not lock.lock("building-high-priority-rpm-for-%s" % config.builder, non_block = 1):
384                 return
385
386         msg = "HIGH PRIORITY: "
387
388     msg += "handling request %s (%d) for %s from %s, priority %s" \
389             % (req.id, req.no, config.builder, req.requester, req.priority)
390     log.notice(msg)
391     status.push(msg)
392     handle_request(req)
393     status.pop()
394
395     def otherreqs(r):
396         if r.no==req.no:
397             return False
398         else:
399             return True
400
401     q = B_Queue(path.queue_file + "-" + config.builder)
402     q.lock(0)
403     q.read()
404     previouslen=len(q.requests)
405     q.requests=filter(otherreqs, q.requests)
406     if len(q.requests)<previouslen:
407         q.write()
408     q.unlock()
409
410 def main():
411     if len(sys.argv) < 2:
412         raise Exception, "fatal: need to have builder name as first arg"
413     return main_for(sys.argv[1])
414
415 if __name__ == '__main__':
416     loop.run_loop(main)