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