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